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 thepre_init
decorator 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.PreSave
is like thepre_save
decorator 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.PostSave
is like thepost_save
decorator 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.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
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,
)
from fake import (
FACTORY,
DjangoModelFactory,
)
class GroupFactory(DjangoModelFactory):
name = FACTORY.word()
class Meta:
model = Group
get_or_create = ("name",)
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 (
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
Breakdown:
username
is a required field. We shouldn’t be usingPreSave
orPostSave
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 whyPreInit
, which operates on thedict
level, from which the model instance is constructed, is used here to construct theusername
value fromfirst_name
and thelast_name
. Theset_username
helper 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.password
is a non-required field and since Django has a well working way for setting it, use ofPreSave
is the best option here. It’s important to mention that functions passed to thePreSave
, do hot 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 whyPostSave
is 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
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
Breakdown:
headline
is a required field that should be available and resolved before the class constructor is called. We already know thatPreInit
should be used for such cases. Theheadline
value is constructed fromcontent
.author
is a foreign key relation field to theUser
model. For foreign key relationsSubFactory
is our best choice.image
is a file field. Files created shall be placed in the path specified inMEDIA_ROOT
Django setting. That’s why we create and configure theSTORAGE
instance to pass it toFACTORY.png_file
in astorage
argument.auto_minutes_to_read
is a required field of theArticle
model. It needs to be resolved and available before the constructor class is called. That’s the@pre_init
decorator is used on theset_auto_minutes_read
helper 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
# 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
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.
SQLModel example
Configuration
Filename: config.py
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
Models
Example SQLModel models closely resemble the earlier shown Django models.
Filename: article/models.py
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"<Group(group='{self.name}')>"
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"<User(username='{self.username}', email='{self.email}')>"
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"<Article(title='{self.title}', author='{self.author.username}')>"
)
See the full example
here
Factories
Example SQLModel factories are identical to the earlier shown SQLAlchemy 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
@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
Used just like in previous example.