Factories
pre_initis a method decorator that will always run before the instance is initialised.pre_saveis a method decorator that will always run before the instance is saved.post_saveis a method decorator that will always run after the instance is saved.traitdecorator runs the code if set to True in factory constructor.PreInitis like thepre_initdecorator of theModelFactory, 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.PreSaveis like thepre_savedecorator of theModelFactory, 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.PostSaveis like thepost_savedecorator of theModelFactory, 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.LazyAttributeexpects a callable, will take the instance as a first argument, runs it with extra arguments specified and sets the value as an attribute name.LazyFunctionexpects a callable, runs it (without any arguments) and sets the value as an attribute name.SubFactoryis 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
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
Factories
Factory for the Django’s built-in Group model could look as simple as this:
Filename: article/factories.py
from django.contrib.auth.models import Group, User
from django.utils import timezone
from fake import (
DjangoModelFactory,
FileSystemStorage,
PreInit,
"""Group factory.
Usage example:
model = Group
get_or_create = ("name",)
def set_password(user: User, password: str) -> None:
See the full example
here
Factory for the Django’s built-in User model could look as this:
Filename: article/factories.py
from typing import Any, Dict
from django.contrib.auth.models import Group, User
from django.utils.text import slugify
from fake import (
FACTORY,
FAKER,
DjangoModelFactory,
PostSave,
SubFactory,
post_save,
pre_init,
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):
"""User factory.
Usage example:
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",)
@post_save
def send_registration_email(self, instance):
instance.is_superuser = True
instance.is_staff = True
instance.is_active = True
@pre_save
def _pre_save_method(self, instance):
See the full example
here
Breakdown:
usernameis a required field. We shouldn’t be usingPreSaveorPostSavemethods 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 whyPreInit, which operates on thedictlevel, from which the model instance is constructed, is used here to construct theusernamevalue fromfirst_nameand thelast_name. Theset_usernamehelper function, which is used byPreInit, 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 thePreInit, do hot have to return anything.passwordis a non-required field and since Django has a well working way for setting it, use ofPreSaveis the best option here. It’s important to mention that functions passed to thePreSave, do hot have to return anything.groupis a non-required many-to-many relationship. We need a user instance to be created before we can add user to groups. That’s whyPostSaveis best option here. It’s important to mention that functions passed to thePostSave, do hot have to return anything.
Factory for the the Artice model could look as this:
Filename: article/factories.py
from django.conf import settings
DjangoModelFactory,
PreSave,
pre_save,
)
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
__copyright__ = "2023-2024 Artur Barseghyan"
# 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):
data["headline"] = data["content"][:25]
class ArticleFactory(DjangoModelFactory):
"""Article factory.
Usage example:
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"]
@pre_save
def _pre_save_method(self, instance):
See the full example
here
Breakdown:
headlineis a required field that should be available and resolved before the class constructor is called. We already know thatPreInitshould be used for such cases. Theheadlinevalue is constructed fromcontent.authoris a foreign key relation field to theUsermodel. For foreign key relationsSubFactoryis our best choice.imageis a file field. Files created shall be placed in the path specified inMEDIA_ROOTDjango setting. That’s why we create and configure theSTORAGEinstance to pass it toFACTORY.png_filein astorageargument.auto_minutes_to_readis a required field of theArticlemodel. It needs to be resolved and available before the constructor class is called. That’s the@pre_initdecorator is used on theset_auto_minutes_readhelper method.
All together it would look as follows:
Filename: article/factories.py
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
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
__copyright__ = "2023-2024 Artur Barseghyan"
# 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):
"""Group factory.
Usage example:
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):
"""User factory.
Usage example:
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",)
@post_save
def send_registration_email(self, instance):
instance.is_superuser = True
instance.is_staff = True
instance.is_active = True
@pre_save
def _pre_save_method(self, instance):
data["headline"] = data["content"][:25]
class ArticleFactory(DjangoModelFactory):
"""Article factory.
Usage example:
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"]
@pre_save
def _pre_save_method(self, instance):
# For testing purposes only
See the full example
here
Usage example
# 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
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
Factories
Example Pydantic factories are almost identical to the earlier shown Django factories.
Filename: article/factories.py
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
Used just like in previous example.
TortoiseORM example
Models
Example TortoiseORM models closely resemble the earlier shown Django models.
Filename: article/models.py
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
Factories
Example TortoiseORM factories are almost identical to the earlier shown Django factories.
Filename: article/factories.py
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
Used just like in previous example.
Dataclasses example
Models
Example dataclass models closely resemble the earlier shown Django models.
Filename: article/models.py
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
Factories
Example dataclass factories are almost identical to the earlier shown Django factories.
Filename: article/factories.py
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
Used just like in previous example.
SQLAlchemy example
Configuration
Filename: config.py
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
Models
Example SQLAlchemy models closely resemble the earlier shown Django models.
Filename: article/models.py
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
Factories
Example SQLAlchemy factories are almost identical to the earlier shown Django factories.
Filename: article/factories.py
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
Used just like in previous example.