Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
2022-07-10
* Add support for type annotations and typing.TypedDict

2022-03-23
* Django: Support for UUIDField

Expand Down
23 changes: 23 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,29 @@ Quick example:

scheme = mixer.blend(Scheme, prop__one=1)

Type annotations and typing.TypedDict
------------
Example:

.. code-block:: python

from mixer.backend.annotated import mixer
from typing import TypedDict

class Test:
one: int
two: int
name: str

class Scheme(TypedDict):
name: str
money: int
male: bool
prop: Test

scheme = mixer.blend(Scheme, name="John", male=True) # {"name": "John", "male": True, ...}



DB commits
----------
Expand Down
60 changes: 60 additions & 0 deletions mixer/backend/annotated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from ..main import Mixer as BaseMixer, TypeMixer as BaseTypeMixer, GenFactory as BaseGenFactory
import typing
import mixer.mix_types as t
import sys
import random


class GenFactory(BaseGenFactory):

@classmethod
def cls_to_simple(cls, fcls):
if hasattr(fcls, "__origin__"):
if fcls.__origin__ == typing.Union:
return random.choice(fcls.__args__)

return super().cls_to_simple(fcls)


class TypeMixer(BaseTypeMixer):
factory = GenFactory


def populate_target(self, values):
if issubclass(self.__scheme, dict):
return self.__scheme(**{k: v for k,v in values})

return super().populate_target(values)

def __load_fields(self):
annotations = getattr(self.__scheme, "__annotations__", None)
if annotations is None:
raise ValueError(f"Class {self.__scheme} has no type annotations")

# https://docs.python.org/3/howto/annotations.html#manually-un-stringizing-stringized-annotations
for fname, prop in annotations.items():
if fname.startswith("_"):
continue


try:
obj_globals = sys.modules[self.__scheme.__module__].__dict__
except (AttributeError, KeyError):
obj_globals = None

if isinstance(prop, typing.ForwardRef):
prop = prop.__forward_arg__

prop_type = eval(prop, obj_globals, dict(vars(self.__scheme)))

yield fname, t.Field(
prop_type,
fname,
)


class Mixer(BaseMixer):
type_mixer_cls = TypeMixer


mixer = Mixer()
2 changes: 1 addition & 1 deletion mixer/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class GenFactory(_.with_metaclass(GenFactoryMeta)):
t.URL: faker.url,
t.UUID: faker.uuid,
t.JSON: faker.json,
type(None): '',

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was there an empty string value?

type(None): lambda: None,
}

fakers = {
Expand Down
1 change: 1 addition & 0 deletions requirements-tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ pony >= 0.7
psycopg2-binary >= 2.8.4

pytest
typing_extensions
49 changes: 49 additions & 0 deletions tests/test_annotated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from __future__ import annotations
import typing as t
import typing_extensions as te
from mixer.backend.annotated import Mixer


class Test:
one: int
two: int
name: str
title: str
body: str
price: t.Optional[int]
something: t.Union[int, str]
choices: list
parts: set
scheme: dict


def test_factory():
from mixer.backend.annotated import GenFactory

g = GenFactory()
test = g.get_fabric(t.Optional[int])
assert isinstance(test(), (int, type(None)))

test = g.get_fabric(t.Union[str, int])
assert isinstance(test(), (str, int))


def test_annotated():
mixer = Mixer()
test = mixer.blend(Test)
assert isinstance(test.price, (int, type(None)))
assert isinstance(test.something, (int, str))


def test_typed_dict():
class TestDict(te.TypedDict):
one: int
two: int
name: str

mixer = Mixer()
test = mixer.blend(TestDict)

assert isinstance(test, dict)
assert "one" in test and "two" in test and "name" in test
assert isinstance(test["one"], int) and isinstance(test["two"], int) and isinstance(test["name"], str)
Loading