# Factories - `pre_init` is a method decorator that will always run before the instance is initialised. - `pre_save` is a method decorator that will always run before the instance is saved. - `post_save` is a method decorator that will always run after the instance is saved. - `trait` decorator runs the code if set to True in factory constructor. - `PreInit` is like the `pre_init` decorator of the `ModelFactory`, but you can pass arguments to it and have a lot of flexibility. See a working example (below) of how set a user password in Django. - `PreSave` is like the `pre_save` decorator of the `ModelFactory`, but you can pass arguments to it and have a lot of flexibility. See a working example (below) of how set a user password in Django. - `PostSave` is like the `post_save` decorator of the `ModelFactory`, but you can pass arguments to it and have a lot of flexibility. See a working example (below) of how to assign a User to a Group after User creation. - `LazyAttribute` expects a callable, will take the instance as a first argument, runs it with extra arguments specified and sets the value as an attribute name. - `LazyFunction` expects a callable, runs it (without any arguments) and sets the value as an attribute name. - `SubFactory` is for specifying relations (typically - ForeignKeys or nested objects). ## Django example ### Models In the `Django` example, we will be using `User` and `Group` models from `django.contrib.auth` sub-package. The `Article` would be the only application specific custom model. *Filename: article/models.py* ```python from django.conf import settings from django.db import models from django.utils import timezone class Article(models.Model): title = models.CharField(max_length=255) slug = models.SlugField(unique=True) content = models.TextField() headline = models.TextField() category = models.CharField(max_length=255) pages = models.IntegerField() auto_minutes_to_read = models.IntegerField() image = models.ImageField(null=True, blank=True) pub_date = models.DateField(default=timezone.now) safe_for_work = models.BooleanField(default=False) minutes_to_read = models.IntegerField(default=5) author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE ) tags = models.JSONField(default=list) ``` *See the full example* [`here`](../examples/django/article/models.py) --- ### Factories Factory for the Django’s built-in `Group` model could look as simple as this: *Filename: article/factories.py* ```python from django.contrib.auth.models import ( Group, ) from fake import ( FACTORY, DjangoModelFactory, ) class GroupFactory(DjangoModelFactory): name = FACTORY.word() class Meta: model = Group get_or_create = ("name",) ``` *See the full example* [`here`](../examples/django/article/factories.py) --- Factory for the Django’s built-in `User` model could look as this: *Filename: article/factories.py* ```python from typing import Any, Dict from django.contrib.auth.models import ( User, ) from django.utils import timezone from django.utils.text import slugify from fake import ( FAKER, PostSave, PreInit, PreSave, ) def set_password(user: User, password: str) -> None: user.set_password(password) def add_to_group(user: User, name: str) -> None: group = GroupFactory(name=name) user.groups.add(group) def set_username(data: Dict[str, Any]) -> None: first_name = slugify(data["first_name"]) last_name = slugify(data["last_name"]) data["username"] = f"{first_name}_{last_name}_{FAKER.pystr().lower()}" class UserFactory(DjangoModelFactory): username = PreInit(set_username) first_name = FACTORY.first_name() last_name = FACTORY.last_name() email = FACTORY.email() date_joined = FACTORY.date_time(tzinfo=timezone.get_current_timezone()) last_login = FACTORY.date_time(tzinfo=timezone.get_current_timezone()) is_superuser = False is_staff = False is_active = FACTORY.pybool() password = PreSave(set_password, password="test1234") group = PostSave(add_to_group, name="TestGroup1234") class Meta: model = User get_or_create = ("username",) @trait def is_admin_user(self, instance: User) -> None: instance.is_superuser = True instance.is_staff = True instance.is_active = True ``` *See the full example* [`here`](../examples/django/article/factories.py) Breakdown: - `username` is a required field. We shouldn’t be using `PreSave` or `PostSave` methods here, because we need it to be available and resolved before calling the class constructor (missing required fields would fail on Pydantic and other frameworks that enforce strong type checking). That’s why `PreInit`, which operates on the `dict` level, from which the model instance is constructed, is used here to construct the `username` value from `first_name` and the `last_name`. The `set_username` helper function, which is used by `PreInit`, accepts a dictionary with model data as argument and all changes to that dictionary are passed further to the class constructor. It’s important to mention that functions passed to the `PreInit`, do not have to return anything. - `password` is a non-required field and since Django provides a built-in way to handle passwords, use of `PreSave` is the best option here. It’s important to mention that functions passed to the `PreSave`, do not have to return anything. - `group` is a non-required many-to-many relationship. We need a User instance to be created before we can add User to a Group. That’s why `PostSave` is the best option here. It’s important to mention that functions passed to the `PostSave`, do not have to return anything. --- A factory for the `Article` model could look like this: *Filename: article/factories.py* ```python from django.conf import settings from fake import ( FileSystemStorage, SubFactory, pre_init, ) from article.models import Article # For Django, all files shall be placed inside `MEDIA_ROOT` directory. # That's why you need to apply this trick - define a # custom `FileSystemStorage` class and pass it to the file factory as # `storage` argument. STORAGE = FileSystemStorage(root_path=settings.MEDIA_ROOT, rel_path="tmp") CATEGORIES = ( "art", "technology", "literature", ) TAGS = ( "painting", "photography", "ai", "data-engineering", "fiction", "poetry", "manual", ) def set_headline(data: Dict[str, Any]) -> None: data["headline"] = data["content"][:25] class ArticleFactory(DjangoModelFactory): title = FACTORY.sentence() slug = FACTORY.slug() content = FACTORY.text() headline = PreInit(set_headline) category = FACTORY.random_choice(elements=CATEGORIES) pages = FACTORY.pyint(min_value=1, max_value=100) image = FACTORY.png_file(storage=STORAGE) pub_date = FACTORY.date(tzinfo=timezone.get_current_timezone()) safe_for_work = FACTORY.pybool() minutes_to_read = FACTORY.pyint(min_value=1, max_value=10) author = SubFactory(UserFactory) tags = FACTORY.random_sample(elements=TAGS, length=3) class Meta: model = Article @pre_init def set_auto_minutes_to_read(self, data: Dict[str, Any]) -> None: data["auto_minutes_to_read"] = data["pages"] ``` *See the full example* [`here`](../examples/django/article/factories.py) Breakdown: - `headline` is a required field that should be available and resolved before the class constructor is called. We already know that `PreInit` should be used for such cases. The `headline` value is constructed from `content`. - `author` is a foreign key relation field to the `User` model. For foreign key relations `SubFactory` is our best choice. - `image` is a file field. Files created shall be placed in the path specified in `MEDIA_ROOT` Django setting. That’s why we create and configure the `STORAGE` instance to pass it to `FACTORY.png_file` in a `storage` argument. - `auto_minutes_to_read` is a required field of the `Article` model. It needs to be resolved and available before the constructor class is called. That’s the `@pre_init` decorator is used on the `set_auto_minutes_read` helper method. --- All together it would look as follows: *Filename: article/factories.py* ```python from typing import Any, Dict from django.conf import settings from django.contrib.auth.models import ( Group, User, ) from django.utils import timezone from django.utils.text import slugify from fake import ( FACTORY, FAKER, DjangoModelFactory, FileSystemStorage, PostSave, PreInit, PreSave, SubFactory, post_save, pre_init, pre_save, trait, ) from article.models import Article # For Django, all files shall be placed inside `MEDIA_ROOT` directory. # That's why you need to apply this trick - define a # custom `FileSystemStorage` class and pass it to the file factory as # `storage` argument. STORAGE = FileSystemStorage(root_path=settings.MEDIA_ROOT, rel_path="tmp") CATEGORIES = ( "art", "technology", "literature", ) TAGS = ( "painting", "photography", "ai", "data-engineering", "fiction", "poetry", "manual", ) class GroupFactory(DjangoModelFactory): name = FACTORY.word() class Meta: model = Group get_or_create = ("name",) def set_password(user: User, password: str) -> None: user.set_password(password) def add_to_group(user: User, name: str) -> None: group = GroupFactory(name=name) user.groups.add(group) def set_username(data: Dict[str, Any]) -> None: first_name = slugify(data["first_name"]) last_name = slugify(data["last_name"]) data["username"] = f"{first_name}_{last_name}_{FAKER.pystr().lower()}" class UserFactory(DjangoModelFactory): username = PreInit(set_username) first_name = FACTORY.first_name() last_name = FACTORY.last_name() email = FACTORY.email() date_joined = FACTORY.date_time(tzinfo=timezone.get_current_timezone()) last_login = FACTORY.date_time(tzinfo=timezone.get_current_timezone()) is_superuser = False is_staff = False is_active = FACTORY.pybool() password = PreSave(set_password, password="test1234") group = PostSave(add_to_group, name="TestGroup1234") class Meta: model = User get_or_create = ("username",) @trait def is_admin_user(self, instance: User) -> None: instance.is_superuser = True instance.is_staff = True instance.is_active = True def set_headline(data: Dict[str, Any]) -> None: data["headline"] = data["content"][:25] class ArticleFactory(DjangoModelFactory): title = FACTORY.sentence() slug = FACTORY.slug() content = FACTORY.text() headline = PreInit(set_headline) category = FACTORY.random_choice(elements=CATEGORIES) pages = FACTORY.pyint(min_value=1, max_value=100) image = FACTORY.png_file(storage=STORAGE) pub_date = FACTORY.date(tzinfo=timezone.get_current_timezone()) safe_for_work = FACTORY.pybool() minutes_to_read = FACTORY.pyint(min_value=1, max_value=10) author = SubFactory(UserFactory) tags = FACTORY.random_sample(elements=TAGS, length=3) class Meta: model = Article @pre_init def set_auto_minutes_to_read(self, data: Dict[str, Any]) -> None: data["auto_minutes_to_read"] = data["pages"] ``` *See the full example* [`here`](../examples/django/article/factories.py) --- **Usage example** ```python # Create one article article = ArticleFactory() # Create 5 articles articles = ArticleFactory.create_batch(5) # Create one article with authors username set to admin. article = ArticleFactory(author__username="admin") # Using trait user = UserFactory(is_admin_user=True) # Using trait in SubFactory article = ArticleFactory(author__is_admin_user=True) # Create a user. Created user will automatically have his password # set to "test1234" and will be added to the group "Test group". user = UserFactory() # Create a user with custom password user = UserFactory( password=PreSave(set_password, password="another-pass"), ) # Add a user to another group user = UserFactory( group=PostSave(add_to_group, name="Another group"), ) # Or even add user to multiple groups at once user = UserFactory( group_1=PostSave(add_to_group, name="Another group"), group_2=PostSave(add_to_group, name="Yet another group"), ) ``` --- ## Pydantic example ### Models Example Pydantic models closely resemble the earlier shown Django models. *Filename: article/models.py* ```python from datetime import date, datetime from typing import List, Optional, Set from fake import xor_transform from pydantic import BaseModel, Field class Group(BaseModel): id: int name: str class Config: allow_mutation = False def __hash__(self): return hash((self.id, self.name)) class User(BaseModel): id: int username: str = Field(max_length=255) first_name: str = Field(max_length=255) last_name: str = Field(max_length=255) email: str = Field(max_length=255) date_joined: datetime = Field(default_factory=datetime.utcnow) last_login: Optional[datetime] = None password: Optional[str] = Field("", max_length=255) is_superuser: bool = Field(default=False) is_staff: bool = Field(default=False) is_active: bool = Field(default=True) groups: Set[Group] = Field(default_factory=set) class Config: extra = "allow" # For testing purposes only def __str__(self): return self.username def set_password(self, password: str) -> None: self.password = xor_transform(password) class Article(BaseModel): id: int title: str = Field(max_length=255) slug: str = Field(max_length=255, unique=True) content: str headline: str category: str = Field(max_length=255) pages: int auto_minutes_to_read: int author: User image: Optional[str] = None # Use str to represent the image path or URL pub_date: date = Field(default_factory=date.today) safe_for_work: bool = False minutes_to_read: int = 5 tags: List[str] = Field(default_factory=list) class Config: extra = "allow" # For testing purposes only def __str__(self): return self.title ``` *See the full example* [`here`](../examples/pydantic/article/models.py) --- ### Factories Example Pydantic factories are almost identical to the earlier shown Django factories. *Filename: article/factories.py* ```python from pathlib import Path from typing import Any, Dict from fake import ( FACTORY, FAKER, FileSystemStorage, PostSave, PreInit, PreSave, PydanticModelFactory, SubFactory, post_save, pre_init, pre_save, slugify, trait, ) from article.models import Article, Group, User # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent MEDIA_ROOT = BASE_DIR / "media" STORAGE = FileSystemStorage(root_path=MEDIA_ROOT, rel_path="tmp") CATEGORIES = ( "art", "technology", "literature", ) TAGS = ( "painting", "photography", "ai", "data-engineering", "fiction", "poetry", "manual", ) class GroupFactory(PydanticModelFactory): id = FACTORY.pyint() name = FACTORY.word() class Meta: model = Group get_or_create = ("name",) def set_password(user: User, password: str) -> None: user.set_password(password) def add_to_group(user: User, name: str) -> None: group = GroupFactory(name=name) user.groups.add(group) def set_username(data: Dict[str, Any]) -> None: first_name = slugify(data["first_name"]) last_name = slugify(data["last_name"]) data["username"] = f"{first_name}_{last_name}_{FAKER.pystr().lower()}" class UserFactory(PydanticModelFactory): id = FACTORY.pyint() username = PreInit(set_username) first_name = FACTORY.first_name() last_name = FACTORY.last_name() email = FACTORY.email() date_joined = FACTORY.date_time() last_login = FACTORY.date_time() is_superuser = False is_staff = False is_active = FACTORY.pybool() password = PreSave(set_password, password="test1234") group = PostSave(add_to_group, name="TestGroup1234") class Meta: model = User @trait def is_admin_user(self, instance: User) -> None: instance.is_superuser = True instance.is_staff = True instance.is_active = True def set_headline(data) -> None: data["headline"] = data["content"][:25] class ArticleFactory(PydanticModelFactory): id = FACTORY.pyint() title = FACTORY.sentence() slug = FACTORY.slug() content = FACTORY.text() headline = PreInit(set_headline) category = FACTORY.random_choice(elements=CATEGORIES) pages = FACTORY.pyint(min_value=1, max_value=100) image = FACTORY.png_file(storage=STORAGE) pub_date = FACTORY.date() safe_for_work = FACTORY.pybool() minutes_to_read = FACTORY.pyint(min_value=1, max_value=10) author = SubFactory(UserFactory) tags = FACTORY.random_sample(elements=TAGS, length=3) class Meta: model = Article @pre_init def set_auto_minutes_to_read(self, data: Dict[str, Any]) -> None: data["auto_minutes_to_read"] = data["pages"] ``` *See the full example* [`here`](../examples/pydantic/article/factories.py) *Used just like in previous example.* --- ## TortoiseORM example #### NOTE TortoiseORM introduces deadlocks from version to version. Currently, last version that worked smoothly with factories of this package was 0.22.2. 0.22.x, 0.20.x and 0.21.x branches worked, while 0.23.x and 0.24.x fail. ### Models Example TortoiseORM models closely resemble the earlier shown Django models. *Filename: article/models.py* ```python from datetime import date from fake import xor_transform from tortoise import fields from tortoise.models import Model class Group(Model): """Group model.""" id = fields.IntField(pk=True) name = fields.CharField(max_length=255, unique=True) class User(Model): """User model.""" id = fields.IntField(pk=True) username = fields.CharField(max_length=255, unique=True) first_name = fields.CharField(max_length=255) last_name = fields.CharField(max_length=255) email = fields.CharField(max_length=255) password = fields.CharField(max_length=255, null=True, blank=True) last_login = fields.DatetimeField(null=True, blank=True) is_superuser = fields.BooleanField(default=False) is_staff = fields.BooleanField(default=False) is_active = fields.BooleanField(default=True) date_joined = fields.DatetimeField(null=True, blank=True) groups = fields.ManyToManyField("models.Group", on_delete=fields.CASCADE) def set_password(self, password: str) -> None: self.password = xor_transform(password) class Article(Model): """Article model.""" id = fields.IntField(pk=True) title = fields.CharField(max_length=255) slug = fields.CharField(max_length=255, unique=True) content = fields.TextField() headline = fields.TextField() category = fields.CharField(max_length=255) pages = fields.IntField() auto_minutes_to_read = fields.IntField() image = fields.TextField(null=True, blank=True) ``` *See the full example* [`here`](../examples/tortoise/article/models.py) --- ### Factories Example TortoiseORM factories are almost identical to the earlier shown Django factories. *Filename: article/factories.py* ```python from pathlib import Path from typing import Any, Dict from fake import ( FACTORY, FAKER, FileSystemStorage, PostSave, PreInit, PreSave, SubFactory, TortoiseModelFactory, post_save, pre_init, pre_save, run_async_in_thread, slugify, trait, ) from article.models import Article, Group, User # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent MEDIA_ROOT = BASE_DIR / "media" STORAGE = FileSystemStorage(root_path=MEDIA_ROOT, rel_path="tmp") CATEGORIES = ( "art", "technology", "literature", ) TAGS = ( "painting", "photography", "ai", "data-engineering", "fiction", "poetry", "manual", ) class GroupFactory(TortoiseModelFactory): """Group factory.""" name = FACTORY.word() class Meta: model = Group get_or_create = ("name",) def set_password(user: User, password: str) -> None: user.set_password(password) def add_to_group(user: User, name: str) -> None: group = GroupFactory(name=name) async def _add_to_group(): await user.groups.add(group) await user.save() run_async_in_thread(_add_to_group()) def set_username(data: Dict[str, Any]) -> None: first_name = slugify(data["first_name"]) last_name = slugify(data["last_name"]) data["username"] = f"{first_name}_{last_name}_{FAKER.pystr().lower()}" class UserFactory(TortoiseModelFactory): """User factory.""" username = PreInit(set_username) first_name = FACTORY.first_name() last_name = FACTORY.last_name() email = FACTORY.email() date_joined = FACTORY.date_time() last_login = FACTORY.date_time() is_superuser = False is_staff = False is_active = FACTORY.pybool() password = PreSave(set_password, password="test1234") group = PostSave(add_to_group, name="TestGroup1234") class Meta: model = User get_or_create = ("username",) @trait def is_admin_user(self, instance: User) -> None: instance.is_superuser = True instance.is_staff = True instance.is_active = True def set_headline(data: Dict[str, Any]) -> None: data["headline"] = data["content"][:25] class ArticleFactory(TortoiseModelFactory): """Article factory.""" title = FACTORY.sentence() slug = FACTORY.slug() content = FACTORY.text() headline = PreInit(set_headline) category = FACTORY.random_choice(elements=CATEGORIES) pages = FACTORY.pyint(min_value=1, max_value=100) image = FACTORY.png_file(storage=STORAGE) pub_date = FACTORY.date() safe_for_work = FACTORY.pybool() minutes_to_read = FACTORY.pyint(min_value=1, max_value=10) author = SubFactory(UserFactory) tags = FACTORY.random_sample(elements=TAGS, length=3) class Meta: model = Article @pre_init def set_auto_minutes_to_read(self, data: Dict[str, Any]) -> None: data["auto_minutes_to_read"] = data["pages"] ``` *See the full example* [`here`](../examples/tortoise/article/factories.py) *Used just like in previous example.* --- ## Dataclasses example ### Models Example dataclass models closely resemble the earlier shown Django models. *Filename: article/models.py* ```python from dataclasses import dataclass, field from datetime import date, datetime from typing import List, Optional, Set from fake import xor_transform @dataclass(frozen=True) class Group: id: int name: str @dataclass class User: id: int username: str first_name: str last_name: str email: str date_joined: datetime = field(default_factory=datetime.utcnow) last_login: Optional[datetime] = None password: Optional[str] = None is_superuser: bool = False is_staff: bool = False is_active: bool = True groups: Set[Group] = field(default_factory=set) def __str__(self): return self.username def set_password(self, password: str) -> None: self.password = xor_transform(password) @dataclass class Article: id: int title: str slug: str content: str headline: str category: str pages: int auto_minutes_to_read: int author: User image: Optional[str] = None # Use str to represent the image path or URL pub_date: date = field(default_factory=date.today) safe_for_work: bool = False minutes_to_read: int = 5 tags: List[str] = field(default_factory=list) def __str__(self): return self.title ``` *See the full example* [`here`](../examples/dataclasses/article/models.py) --- ### Factories Example dataclass factories are almost identical to the earlier shown Django factories. *Filename: article/factories.py* ```python from pathlib import Path from typing import Any, Dict from fake import ( FACTORY, FAKER, FileSystemStorage, ModelFactory, PostSave, PreInit, PreSave, SubFactory, post_save, pre_init, pre_save, slugify, trait, ) from article.models import Article, Group, User # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent MEDIA_ROOT = BASE_DIR / "media" STORAGE = FileSystemStorage(root_path=MEDIA_ROOT, rel_path="tmp") CATEGORIES = ( "art", "technology", "literature", ) TAGS = ( "painting", "photography", "ai", "data-engineering", "fiction", "poetry", "manual", ) class GroupFactory(ModelFactory): id = FACTORY.pyint() name = FACTORY.word() class Meta: model = Group get_or_create = ("name",) def set_password(user: User, password: str) -> None: user.set_password(password) def add_to_group(user: User, name: str) -> None: group = GroupFactory(name=name) user.groups.add(group) def set_username(data: Dict[str, Any]) -> None: first_name = slugify(data["first_name"]) last_name = slugify(data["last_name"]) data["username"] = f"{first_name}_{last_name}_{FAKER.pystr().lower()}" class UserFactory(ModelFactory): id = FACTORY.pyint() username = PreInit(set_username) first_name = FACTORY.first_name() last_name = FACTORY.last_name() email = FACTORY.email() date_joined = FACTORY.date_time() last_login = FACTORY.date_time() is_superuser = False is_staff = False is_active = FACTORY.pybool() password = PreSave(set_password, password="test1234") group = PostSave(add_to_group, name="TestGroup1234") class Meta: model = User @trait def is_admin_user(self, instance: User) -> None: instance.is_superuser = True instance.is_staff = True instance.is_active = True def set_headline(data: Dict[str, Any]) -> None: data["headline"] = data["content"][:25] class ArticleFactory(ModelFactory): id = FACTORY.pyint() title = FACTORY.sentence() slug = FACTORY.slug() content = FACTORY.text() headline = PreInit(set_headline) category = FACTORY.random_choice(elements=CATEGORIES) pages = FACTORY.pyint(min_value=1, max_value=100) image = FACTORY.png_file(storage=STORAGE) pub_date = FACTORY.date() safe_for_work = FACTORY.pybool() minutes_to_read = FACTORY.pyint(min_value=1, max_value=10) author = SubFactory(UserFactory) tags = FACTORY.random_sample(elements=TAGS, length=3) class Meta: model = Article @pre_init def set_auto_minutes_to_read(self, data: Dict[str, Any]) -> None: data["auto_minutes_to_read"] = data["pages"] ``` *See the full example* [`here`](../examples/dataclasses/article/factories.py) *Used just like in previous example.* --- ## SQLAlchemy example ### Configuration *Filename: config.py* ```python from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker # SQLAlchemy DATABASE_URL = "sqlite:///test_database.db" ENGINE = create_engine(DATABASE_URL) SESSION = scoped_session(sessionmaker(bind=ENGINE)) ``` *See the full example* [`here`](../examples/sqlalchemy/config.py) --- ### Models Example SQLAlchemy models closely resemble the earlier shown Django models. *Filename: article/models.py* ```python from datetime import datetime from fake import xor_transform from sqlalchemy import ( JSON, Boolean, Column, DateTime, ForeignKey, Integer, String, Table, Text, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship Base = declarative_base() # Association table for the many-to-many relationship user_group_association = Table( "user_group", Base.metadata, Column("user_id", Integer, ForeignKey("users.id")), Column("group_id", Integer, ForeignKey("groups.id")), ) class Group(Base): """Group model.""" __tablename__ = "groups" id = Column(Integer, primary_key=True) name = Column(String(255), unique=True) class User(Base): """User model.""" __tablename__ = "users" id = Column(Integer, primary_key=True) username = Column(String(255), unique=True) first_name = Column(String(255)) last_name = Column(String(255)) email = Column(String(255)) date_joined = Column(DateTime, default=datetime.utcnow) last_login = Column(DateTime, nullable=True) password = Column(String(255), nullable=True) is_superuser = Column(Boolean, default=False) is_staff = Column(Boolean, default=False) is_active = Column(Boolean, default=True) # Many-to-many relationship groups = relationship( "Group", secondary=user_group_association, backref="users" ) # One-to-many relationship articles = relationship("Article", back_populates="author") def set_password(self, password: str) -> None: self.password = xor_transform(password) class Article(Base): """Article model.""" __tablename__ = "articles" id = Column(Integer, primary_key=True) title = Column(String(255)) slug = Column(String(255), unique=True) content = Column(Text) headline = Column(Text) category = Column(String(255)) pages = Column(Integer) auto_minutes_to_read = Column(Integer) image = Column(Text, nullable=True) pub_date = Column(DateTime, default=datetime.utcnow) safe_for_work = Column(Boolean, default=False) minutes_to_read = Column(Integer, default=5) author_id = Column(Integer, ForeignKey("users.id")) tags = Column(JSON, default=list) # Relationships author = relationship("User", back_populates="articles") ``` *See the full example* [`here`](../examples/sqlalchemy/article/models.py) --- ### Factories Example SQLAlchemy factories are almost identical to the earlier shown Django factories. *Filename: article/factories.py* ```python from pathlib import Path from typing import Any, Dict from fake import ( FACTORY, FAKER, FileSystemStorage, PostSave, PreInit, PreSave, SQLAlchemyModelFactory, SubFactory, post_save, pre_init, pre_save, slugify, trait, ) from article.models import Article, Group, User from config import SESSION # Storage config. Build paths inside the project like this: BASE_DIR / 'subdir' BASE_DIR = Path(__file__).resolve().parent.parent MEDIA_ROOT = BASE_DIR / "media" STORAGE = FileSystemStorage(root_path=MEDIA_ROOT, rel_path="tmp") CATEGORIES = ( "art", "technology", "literature", ) TAGS = ( "painting", "photography", "ai", "data-engineering", "fiction", "poetry", "manual", ) def get_session(): return SESSION() class GroupFactory(SQLAlchemyModelFactory): """User factory.""" name = FACTORY.word() class Meta: model = Group get_or_create = ("name",) class MetaSQLAlchemy: get_session = get_session def set_password(user: User, password: str) -> None: user.set_password(password) def add_to_group(user: User, name: str) -> None: session = get_session() # Check if the group already exists group = session.query(Group).filter_by(name=name).first() # If the group doesn't exist, create a new one if not group: group = Group(name=name) session.add(group) session.commit() # Commit to assign an ID to the new group # Add the group to the user's groups using append if group not in user.groups: user.groups.append(group) session.commit() # Commit the changes def set_username(data: Dict[str, Any]) -> None: first_name = slugify(data["first_name"]) last_name = slugify(data["last_name"]) data["username"] = f"{first_name}_{last_name}_{FAKER.pystr().lower()}" class UserFactory(SQLAlchemyModelFactory): """User factory.""" username = PreInit(set_username) first_name = FACTORY.first_name() last_name = FACTORY.last_name() email = FACTORY.email() date_joined = FACTORY.date_time() last_login = FACTORY.date_time() is_superuser = False is_staff = False is_active = FACTORY.pybool() password = PreSave(set_password, password="test1234") group = PostSave(add_to_group, name="TestGroup1234") class Meta: model = User get_or_create = ("username",) class MetaSQLAlchemy: get_session = get_session @trait def is_admin_user(self, instance: User) -> None: instance.is_superuser = True instance.is_staff = True instance.is_active = True def set_headline(data: Dict[str, Any]) -> None: data["headline"] = data["content"][:25] class ArticleFactory(SQLAlchemyModelFactory): """Article factory.""" title = FACTORY.sentence() slug = FACTORY.slug() content = FACTORY.text() headline = PreInit(set_headline) category = FACTORY.random_choice(elements=CATEGORIES) pages = FACTORY.pyint(min_value=1, max_value=100) image = FACTORY.png_file(storage=STORAGE) pub_date = FACTORY.date() safe_for_work = FACTORY.pybool() minutes_to_read = FACTORY.pyint(min_value=1, max_value=10) author = SubFactory(UserFactory) tags = FACTORY.random_sample(elements=TAGS, length=3) class Meta: model = Article class MetaSQLAlchemy: get_session = get_session @pre_init def set_auto_minutes_to_read(self, data: Dict[str, Any]) -> None: data["auto_minutes_to_read"] = data["pages"] ``` *See the full example* [`here`](../examples/sqlalchemy/article/factories.py) *Used just like in previous example.* --- ## SQLModel example ### Configuration *Filename: config.py* ```python from sqlalchemy.orm import scoped_session, sessionmaker from sqlmodel import SQLModel, create_engine # SQLAlchemy DATABASE_URL = "sqlite:///test_database.db" ENGINE = create_engine(DATABASE_URL) SESSION = scoped_session(sessionmaker(bind=ENGINE)) def create_tables(): """Create tables.""" SQLModel.metadata.create_all(ENGINE) def get_db(): """Get database connection.""" session = SESSION() try: yield session session.commit() finally: session.close() if __name__ == "__main__": create_tables() ``` *See the full example* [`here`](../examples/sqlmodel/config.py) --- ### Models Example SQLModel models closely resemble the earlier shown Django models. *Filename: article/models.py* ```python from datetime import datetime from typing import List, Optional from fake import xor_transform from sqlmodel import JSON, Column, Field, Relationship, SQLModel class UserGroup(SQLModel, table=True): user_id: Optional[int] = Field( default=None, foreign_key="user.id", primary_key=True, ) group_id: Optional[int] = Field( default=None, foreign_key="group.id", primary_key=True, ) class Group(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(sa_column_kwargs={"unique": True}) users: List["User"] = Relationship( back_populates="groups", link_model=UserGroup, ) def __repr__(self): return f"" class User(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) username: str = Field(sa_column_kwargs={"unique": True}) first_name: str last_name: str email: str date_joined: datetime = Field(default_factory=datetime.utcnow) last_login: Optional[datetime] password: str is_superuser: bool = Field(default=False) is_staff: bool = Field(default=False) is_active: bool = Field(default=True) groups: List[Group] = Relationship( back_populates="users", link_model=UserGroup, ) articles: List["Article"] = Relationship(back_populates="author") def __repr__(self): return f"" def set_password(self, password: str) -> None: self.password = xor_transform(password) class Article(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) title: str slug: str = Field(sa_column_kwargs={"unique": True}) content: str headline: str category: str pages: int auto_minutes_to_read: int image: Optional[str] pub_date: datetime = Field(default_factory=datetime.utcnow) safe_for_work: bool = Field(default=False) minutes_to_read: int = Field(default=5) author_id: int = Field(foreign_key="user.id") tags: Optional[List[str]] = Field( default_factory=list, sa_column=Column(JSON), ) author: User = Relationship(back_populates="articles") # Needed for Column(JSON) class Config: arbitrary_types_allowed = True def __repr__(self): return ( f"" ) ``` *See the full example* [`here`](../examples/sqlmodel/article/models.py) --- ### Factories Example SQLModel factories are identical to the earlier shown SQLAlchemy factories. *Filename: article/factories.py* ```python from pathlib import Path from typing import Any, Dict from fake import ( FACTORY, FAKER, FileSystemStorage, PostSave, PreInit, PreSave, SQLAlchemyModelFactory, SubFactory, post_save, pre_init, pre_save, slugify, trait, ) from article.models import Article, Group, User from config import SESSION # Storage config. Build paths inside the project like this: BASE_DIR / 'subdir' BASE_DIR = Path(__file__).resolve().parent.parent MEDIA_ROOT = BASE_DIR / "media" STORAGE = FileSystemStorage(root_path=MEDIA_ROOT, rel_path="tmp") CATEGORIES = ( "art", "technology", "literature", ) TAGS = ( "painting", "photography", "ai", "data-engineering", "fiction", "poetry", "manual", ) def get_session(): return SESSION() class GroupFactory(SQLAlchemyModelFactory): """User factory.""" name = FACTORY.word() class Meta: model = Group get_or_create = ("name",) class MetaSQLAlchemy: get_session = get_session def set_password(user: User, password: str) -> None: user.set_password(password) def add_to_group(user: User, name: str) -> None: session = get_session() # Check if the group already exists group = session.query(Group).filter_by(name=name).first() # If the group doesn't exist, create a new one if not group: group = Group(name=name) session.add(group) session.commit() # Commit to assign an ID to the new group # Add the group to the user's groups using append if group not in user.groups: user.groups.append(group) session.commit() # Commit the changes def set_username(data: Dict[str, Any]) -> None: first_name = slugify(data["first_name"]) last_name = slugify(data["last_name"]) data["username"] = f"{first_name}_{last_name}_{FAKER.pystr().lower()}" class UserFactory(SQLAlchemyModelFactory): """User factory.""" username = PreInit(set_username) first_name = FACTORY.first_name() last_name = FACTORY.last_name() email = FACTORY.email() date_joined = FACTORY.date_time() last_login = FACTORY.date_time() is_superuser = False is_staff = False is_active = FACTORY.pybool() password = PreSave(set_password, password="test1234") group = PostSave(add_to_group, name="TestGroup1234") class Meta: model = User get_or_create = ("username",) class MetaSQLAlchemy: get_session = get_session @trait def is_admin_user(self, instance: User) -> None: instance.is_superuser = True instance.is_staff = True instance.is_active = True @pre_save def _pre_save_method(self, instance): # For testing purposes only instance._pre_save_called = True @post_save def _post_save_method(self, instance): # For testing purposes only instance._post_save_called = True def set_headline(data: Dict[str, Any]) -> None: data["headline"] = data["content"][:25] class ArticleFactory(SQLAlchemyModelFactory): """Article factory.""" title = FACTORY.sentence() slug = FACTORY.slug() content = FACTORY.text() headline = PreInit(set_headline) category = FACTORY.random_choice(elements=CATEGORIES) pages = FACTORY.pyint(min_value=1, max_value=100) image = FACTORY.png_file(storage=STORAGE) pub_date = FACTORY.date() safe_for_work = FACTORY.pybool() minutes_to_read = FACTORY.pyint(min_value=1, max_value=10) author = SubFactory(UserFactory) tags = FACTORY.random_sample(elements=TAGS, length=3) class Meta: model = Article class MetaSQLAlchemy: get_session = get_session @pre_init def set_auto_minutes_to_read(self, data: Dict[str, Any]) -> None: data["auto_minutes_to_read"] = data["pages"] @pre_save def _pre_save_method(self, instance): # For testing purposes only instance._pre_save_called = True @post_save def _post_save_method(self, instance): # For testing purposes only instance._post_save_called = True ``` *See the full example* [`here`](../examples/sqlmodel/article/factories.py) *Used just like in previous example.* ---