diff --git a/examples/dataclasses/article/factories.py b/examples/dataclasses/article/factories.py index bb6e2a8..056ac93 100644 --- a/examples/dataclasses/article/factories.py +++ b/examples/dataclasses/article/factories.py @@ -19,6 +19,7 @@ __license__ = "MIT" __all__ = ( "ArticleFactory", + "GroupFactory", "UserFactory", ) @@ -53,11 +54,11 @@ class UserFactory(ModelFactory): first_name = FACTORY.first_name() # type: ignore last_name = FACTORY.last_name() # type: ignore email = FACTORY.email() # type: ignore + date_joined = FACTORY.date_time() # type: ignore last_login = FACTORY.date_time() # type: ignore is_superuser = False is_staff = False is_active = FACTORY.pybool() # type: ignore - date_joined = FACTORY.date_time() # type: ignore password = PreSave(set_password, password="test1234") # type: ignore group = PostSave(add_to_group, name="TestGroup1234") # type: ignore diff --git a/examples/dataclasses/article/models.py b/examples/dataclasses/article/models.py index e5ada62..b6d9d2a 100644 --- a/examples/dataclasses/article/models.py +++ b/examples/dataclasses/article/models.py @@ -2,29 +2,18 @@ from datetime import date, datetime from typing import Optional, Set +from fake import xor_transform + __author__ = "Artur Barseghyan " __copyright__ = "2023 Artur Barseghyan" __license__ = "MIT" __all__ = ( "Article", + "Group", "User", ) -def xor_transform(val: str, key: int = 10) -> str: - """Simple, deterministic string encoder/decoder. - - Usage example: - - .. code-block:: python - - val = "abcd" - encoded_val = xor_transform(val) - decoded_val = xor_transform(encoded_val) - """ - return "".join(chr(ord(__c) ^ key) for __c in val) - - @dataclass(frozen=True) class Group: id: int diff --git a/examples/dataclasses/article/tests.py b/examples/dataclasses/article/tests.py index c5a70b2..2d99b85 100644 --- a/examples/dataclasses/article/tests.py +++ b/examples/dataclasses/article/tests.py @@ -1,10 +1,10 @@ import unittest from datetime import datetime -from fake import FILE_REGISTRY +from fake import FILE_REGISTRY, xor_transform from article.factories import ArticleFactory, UserFactory -from article.models import Article, User, xor_transform +from article.models import Article, User __author__ = "Artur Barseghyan " __copyright__ = "2023 Artur Barseghyan" diff --git a/examples/django/article/factories.py b/examples/django/article/factories.py index 94efd37..b443eb8 100644 --- a/examples/django/article/factories.py +++ b/examples/django/article/factories.py @@ -40,8 +40,8 @@ def set_password(user: User, password: str) -> None: user.set_password(password) -def add_to_group(user: User, group_name: str) -> None: - group = GroupFactory(name=group_name) +def add_to_group(user: User, name: str) -> None: + group = GroupFactory(name=name) user.groups.add(group) @@ -60,13 +60,13 @@ class UserFactory(DjangoModelFactory): 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() - date_joined = FACTORY.date_time(tzinfo=timezone.get_current_timezone()) password = PreSave(set_password, password="test1234") - group = PostSave(add_to_group, group_name="TestGroup1234") + group = PostSave(add_to_group, name="TestGroup1234") class Meta: model = User diff --git a/examples/pydantic/article/factories.py b/examples/pydantic/article/factories.py index 011f9d9..99783ca 100644 --- a/examples/pydantic/article/factories.py +++ b/examples/pydantic/article/factories.py @@ -4,19 +4,22 @@ FACTORY, FileSystemStorage, ModelFactory, + PostSave, + PreSave, SubFactory, post_save, pre_save, trait, ) -from article.models import Article, User +from article.models import Article, Group, User __author__ = "Artur Barseghyan " __copyright__ = "2023 Artur Barseghyan" __license__ = "MIT" __all__ = ( "ArticleFactory", + "GroupFactory", "UserFactory", ) @@ -27,17 +30,37 @@ STORAGE = FileSystemStorage(root_path=MEDIA_ROOT, rel_path="tmp") +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) + + class UserFactory(ModelFactory): id = FACTORY.pyint() username = FACTORY.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() - date_joined = FACTORY.date_time() + password = PreSave(set_password, password="test1234") # type: ignore + group = PostSave(add_to_group, name="TestGroup1234") # type: ignore class Meta: model = User diff --git a/examples/pydantic/article/models.py b/examples/pydantic/article/models.py index d5a31d2..10b1172 100644 --- a/examples/pydantic/article/models.py +++ b/examples/pydantic/article/models.py @@ -1,6 +1,7 @@ from datetime import date, datetime -from typing import Optional +from typing import Optional, Set +from fake import xor_transform from pydantic import BaseModel, Field __author__ = "Artur Barseghyan " @@ -8,22 +9,40 @@ __license__ = "MIT" __all__ = ( "Article", + "Group", "User", ) +class Group(BaseModel): + id: int + name: str + + class Config: + allow_mutation = False + + def __hash__(self): + return hash((self.id, self.name)) + + def __eq__(self, other): + if isinstance(other, Group): + return self.id == other.id and self.name == other.name + return False + + 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) - password: Optional[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) - date_joined: Optional[datetime] = None + groups: Set[Group] = Field(default_factory=set) class Config: extra = "allow" # For testing purposes only @@ -31,6 +50,9 @@ class Config: def __str__(self): return self.username + def set_password(self, password: str) -> None: + self.password = xor_transform(password) + class Article(BaseModel): id: int diff --git a/examples/pydantic/article/tests.py b/examples/pydantic/article/tests.py index 3994cc5..2d99b85 100644 --- a/examples/pydantic/article/tests.py +++ b/examples/pydantic/article/tests.py @@ -1,9 +1,9 @@ import unittest from datetime import datetime -from fake import FILE_REGISTRY +from fake import FILE_REGISTRY, xor_transform -from article.factories import ArticleFactory +from article.factories import ArticleFactory, UserFactory from article.models import Article, User __author__ = "Artur Barseghyan " @@ -49,3 +49,12 @@ def test_sub_factory(self) -> None: articles = ArticleFactory.create_batch(5) self.assertEqual(len(articles), 5) self.assertIsInstance(articles[0], Article) + + def test_pre_save_and_post_save(self) -> None: + """Test PreSave and PostSave.""" + user = UserFactory(is_staff=True) + self.assertEqual( + xor_transform(user.password), + "test1234", + ) + self.assertEqual(list(user.groups)[0].name, "TestGroup1234") diff --git a/examples/sqlalchemy/article/factories.py b/examples/sqlalchemy/article/factories.py index ec784a3..aba859c 100644 --- a/examples/sqlalchemy/article/factories.py +++ b/examples/sqlalchemy/article/factories.py @@ -3,6 +3,8 @@ from fake import ( FACTORY, FileSystemStorage, + PostSave, + PreSave, SQLAlchemyModelFactory, SubFactory, post_save, @@ -10,7 +12,7 @@ trait, ) -from article.models import Article, User +from article.models import Article, Group, User from config import SESSION __author__ = "Artur Barseghyan " @@ -31,6 +33,40 @@ 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 + + class UserFactory(SQLAlchemyModelFactory): """User factory.""" @@ -38,11 +74,13 @@ class UserFactory(SQLAlchemyModelFactory): first_name = FACTORY.first_name() # mypy: ignore last_name = FACTORY.last_name() # mypy: ignore email = FACTORY.email() # mypy: ignore + date_joined = FACTORY.date_time() # mypy: ignore last_login = FACTORY.date_time() # mypy: ignore is_superuser = False is_staff = False is_active = FACTORY.pybool() # mypy: ignore - date_joined = FACTORY.date_time() # mypy: ignore + password = PreSave(set_password, password="test1234") # type: ignore + group = PostSave(add_to_group, name="TestGroup1234") # type: ignore class Meta: model = User diff --git a/examples/sqlalchemy/article/models.py b/examples/sqlalchemy/article/models.py index e3a046e..586661b 100644 --- a/examples/sqlalchemy/article/models.py +++ b/examples/sqlalchemy/article/models.py @@ -1,5 +1,6 @@ from datetime import datetime +from fake import xor_transform from sqlalchemy import ( Boolean, Column, @@ -7,6 +8,7 @@ ForeignKey, Integer, String, + Table, Text, ) from sqlalchemy.ext.declarative import declarative_base @@ -25,6 +27,27 @@ 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) + + def __repr__(self): + return f"" + + class User(Base): """User model.""" @@ -35,18 +58,27 @@ class User(Base): first_name = Column(String(255)) last_name = Column(String(255)) email = Column(String(255)) - password = Column(String(255), nullable=True) + 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) - date_joined = Column(DateTime, nullable=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 __repr__(self): return f"" + def set_password(self, password: str) -> None: + self.password = xor_transform(password) + class Article(Base): """Article model.""" diff --git a/examples/sqlalchemy/article/tests.py b/examples/sqlalchemy/article/tests.py index 9f0f996..3bd82a9 100644 --- a/examples/sqlalchemy/article/tests.py +++ b/examples/sqlalchemy/article/tests.py @@ -1,5 +1,7 @@ import unittest +from fake import xor_transform + from article.factories import ArticleFactory, UserFactory from config import SESSION @@ -29,3 +31,12 @@ def test_article_creation(self): article = ArticleFactory(title="Test Article", author=user) self.assertIsNotNone(article.id) self.assertEqual(article.author.username, "authoruser") + + def test_pre_save_and_post_save(self) -> None: + """Test PreSave and PostSave.""" + user = UserFactory(is_staff=True) + self.assertEqual( + xor_transform(user.password), + "test1234", + ) + self.assertEqual(list(user.groups)[0].name, "TestGroup1234") diff --git a/examples/tortoise/article/factories.py b/examples/tortoise/article/factories.py index 66a50e5..ed961cf 100644 --- a/examples/tortoise/article/factories.py +++ b/examples/tortoise/article/factories.py @@ -3,20 +3,24 @@ from fake import ( FACTORY, FileSystemStorage, + PostSave, + PreSave, SubFactory, TortoiseModelFactory, post_save, pre_save, + run_async_in_thread, trait, ) -from article.models import Article, User +from article.models import Article, Group, User __author__ = "Artur Barseghyan " __copyright__ = "2023 Artur Barseghyan" __license__ = "MIT" __all__ = ( "ArticleFactory", + "GroupFactory", "UserFactory", ) @@ -27,6 +31,30 @@ STORAGE = FileSystemStorage(root_path=MEDIA_ROOT, rel_path="tmp") +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()) + + class UserFactory(TortoiseModelFactory): """User factory.""" @@ -34,11 +62,13 @@ class UserFactory(TortoiseModelFactory): 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() - date_joined = FACTORY.date_time() + password = PreSave(set_password, password="test1234") + group = PostSave(add_to_group, name="TestGroup1234") class Meta: model = User diff --git a/examples/tortoise/article/models.py b/examples/tortoise/article/models.py index 868da62..adbe947 100644 --- a/examples/tortoise/article/models.py +++ b/examples/tortoise/article/models.py @@ -1,5 +1,6 @@ from datetime import date +from fake import xor_transform from tortoise import fields from tortoise.models import Model @@ -8,10 +9,21 @@ __license__ = "MIT" __all__ = ( "Article", + "Group", "User", ) +class Group(Model): + """Group model.""" + + id = fields.IntField(pk=True) + name = fields.CharField(max_length=255, unique=True) + + def __str__(self): + return self.name + + class User(Model): """User model.""" @@ -26,10 +38,14 @@ class User(Model): 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 __str__(self): return self.title + def set_password(self, password: str) -> None: + self.password = xor_transform(password) + class Article(Model): """Article model.""" diff --git a/examples/tortoise/article/tests.py b/examples/tortoise/article/tests.py index 3046ec8..a5c16a0 100644 --- a/examples/tortoise/article/tests.py +++ b/examples/tortoise/article/tests.py @@ -1,10 +1,10 @@ from datetime import datetime -from fake import FILE_REGISTRY +from fake import FILE_REGISTRY, run_async_in_thread, xor_transform from tortoise.contrib import test from tortoise.contrib.test import finalizer, initializer -from article.factories import ArticleFactory +from article.factories import ArticleFactory, UserFactory from article.models import Article, User __author__ = "Artur Barseghyan " @@ -98,3 +98,20 @@ def test_sub_factory(self) -> None: articles = ArticleFactory.create_batch(5) self.assertEqual(len(articles), 5) self.assertIsInstance(articles[0], Article) + + def test_pre_save_and_post_save(self) -> None: + """Test PreSave and PostSave.""" + user = UserFactory(is_staff=True, is_active=True) + self.assertEqual( + xor_transform(user.password), + "test1234", + ) + + async def get_first_group_name(): + first_group = await user.groups.all().first() + if first_group: + return first_group.name + return None + + group_name = run_async_in_thread(get_first_group_name()) + self.assertEqual(group_name, "TestGroup1234") diff --git a/fake.py b/fake.py index 4a8998c..55e0d81 100644 --- a/fake.py +++ b/fake.py @@ -79,6 +79,7 @@ "provider", "run_async_in_thread", "trait", + "xor_transform", ) LOGGER = logging.getLogger(__name__) @@ -2460,6 +2461,13 @@ class BaseDataFiller: "headline": FAKER.sentence, "first_name": FAKER.first_name, "last_name": FAKER.last_name, + "uuid": FAKER.uuid, + "body": FAKER.text, + "summary": FAKER.paragraph, + "date_of_birth": FAKER.date, + "dob": FAKER.date, + "age": partial(FAKER.pyint, min_value=1, max_value=100), + "url": FAKER.url, } @classmethod