How to use factory.LazyAttribute with Faker() functions - python

I am using factory_boy to build some fixtures in Django.
I want to use factory.LazyAttribute to build one attribute based on the condition of another attribute.
class MyFactory(factory.django.DjangoModelFactory):
class Meta:
model = MyModel
title = 'My Title' if random() < 0.5 else None
description = factory.LazyAttribute(
lambda a: factory.Faker(
'paragraph', nb_sentences=1, variable_nb_sentences=False
) if a.title else None)
However, this returns a string being <factory.faker.Faker object at 0x000001B10597BB20> rather than executing the correct paragraph generation.
Where am I going wrong?

factory.Faker is a special object: when a factory instantiates an object, it will ask the factory.Faker proxy to get a random value from faker.
The simplest way to achieve what you're looking for is to use factory.Maybe:
class MyFactory(factory.django.DjangoModelFactory):
class Meta:
model = MyModel
title = factory.fuzzy.FuzzyChoice(["My Title", None])
description = factory.Maybe('title', factory.Faker('paragraph'))
Note that, in the code you shared, the title = "My title" if random() < 0.5 else None is computed exactly once, when Python parses the file. I've used factory.fuzzy.FuzzyChoice to have that random computation performed for each object. This function also uses factory_boy's randomness management features.
Another option would be to use parameters (class Params):
class MyFactory(factory.django.DjangoModelFactory):
class Meta:
model = MyModel
class Params:
# Items here will be available to further declarations, but won't be
# passed to the model constructor.
description_contents = factory.Faker('paragraph')
title = factory.fuzzy.FuzzyChoice(["My Title", None])
description = factory.LazyAttribute(lambda a: a.description_contents if a.title else None)

Or if you need a more specific interaction with the Faker library:
from faker import Faker as RealFaker
real_faker = RealFaker()
Inside the factory:
factory.LazyAttribute(lambda a: real_faker.email())

Related

Set parent class attribute in child class constructor

This question is posed as a general Python question, but will be exemplified using Kubeflow Pipelines SDK, kfp==1.8.12. In this module, I want to create a helper class around the class dsl.ContainerOp to simplify a lot of my work. As a minimal example, below I use this class to create a component as such:
from kfp import dsl
name = 'My name'
image = 'My image'
docker_entrypoint = "/main.py"
docker_args = [
'--arg1', 'some arg',
'--arg2', 'some other arg'
]
component = dsl.ContainerOp(
name=name,
image=image,
arguments=[docker_entrypoint] + docker_args
)
Then, I would like to set one of its attributes that relates to caching as such;
use_caching = False
if use_caching:
staleness = "P30D"
else:
staleness = "P0D"
component.execution_options.caching_strategy.max_cache_staleness = staleness
which works fine, as expected. Now, I would like to create a ContainerOpHelper class, to simplify a lot of my argument passing (the "real" code has a lot of parameters). Problem: I need to access the attribute execution_options.caching_strategy.max_cache_staleness from the class, but I can't figure out how! Here is the helper class, and my attempt to access the attribute;
class ContainerOpHelper(dsl.ContainerOp):
def __init__(
self,
name: str,
image: str,
docker_entrypoint: str = None,
docker_args: list = None,
use_caching: bool = None
):
super().__init__(
name=name,
image=image,
arguments=([docker_entrypoint] if docker_entrypoint else []) + (docker_args if docker_args else [])
)
if use_caching:
staleness = "P30D"
else:
staleness = "P0D"
# Tried to be creative; but doesnt work
super.__setattr__("execution_options.caching_strategy.max_cache_staleness", staleness)
This helper class can then be used as such;
component = ContainerOpHelper(
name='My name',
image='My image',
docker_entrypoint="/main.py",
docker_args=[
'--arg1', 'some arg',
'--arg2', 'some other arg'
],
use_caching=False
)
Since the attribute execution_options.caching_strategy.max_cache_staleness is "many levels deep", I'm not sure how I can set it in my helper class. Any ideas?
The solution was fairly simple, as provided by the comment of #quamrana.
Simply set it straight in the child class constructor as such;
self.execution_options.caching_strategy.max_cache_staleness = staleness

In Django, how do I define a string value for IntegerChoices enum?

I'm using Django 3.2 and Python 3.9. In my model, I define an int enum. I would also like to define readable string values for it, so I tried
class Transaction(models.Model):
class TransactionTypes(models.IntegerChoices):
BUY = 0
SELL = 1
labels = {
BUY: 'Buy',
SELL: 'Sell'
}
translation = {v: k for k, v in labels.items()}
but this definition fails with the error
TypeError: int() argument must be a string, a bytes-like object or a number, not 'dict'
How would I define strings for each value? I don't mind if the strings are just the literal variable names (e.g. "BUY", "SELL")
Edit: In response to the one of the answers given, seeing this result ...
>>> t = Transaction.objects.all().first()
>>> t.type
0
>>> str(t.type)
'0'
A simpler way to go about it as per django official documentation for Django3.2
class Transaction(models.Model):
class TransactionTypes(models.IntegerChoices):
BUY = 0, _('Buy')
SELL = 1, _('Sell')
(or)
class Transaction(models.Model):
class TransactionTypes(models.IntegerChoices):
BUY = 0, 'Buy'
SELL = 1, 'Sell'
Another way is to make use of Enum functional api, this is also mentioned in the Django 3.2 official documentation
TransactionTypes = models.IntegerChoices('TransactionTypes', 'BUY SELL')
TransactionTypes.choices
#provides below output
>>>[(1, 'Buy'), (2, 'Sell')]
EDIT: 1
Considering you only have a handful of transaction types (like Buy/Sell and other future transaction type possibilities like Exchange or Return), I would suggest to use PositiveSmallIntegerField which is more apt for your scenario.
Here PositiveSmallIntegerField supports values from 0 to 32767 compared to SmallIntegerField supports values from -32768 to 32767
SYNTAX:
models.PositiveSmallIntegerField(**Field Options)
Example:
class Transaction(models.Model):
class TransactionTypes(models.IntegerChoices):
BUY = 0, 'Buy'
SELL = 1, 'Sell'
start_transactionType= models.PositiveSmallIntegerField(choices=TransactionTypes.choices, default=TransactionTypes.BUY, help_text="Do you wish to Buy or Sell?", null=True, blank=True, primary_key=False, editable=True)
def __str__(self):
return '%s' % (self.start_transactionType)
__ str __ is a Python method that returns a string representation of any object. This is what Django uses to display model instances as a plain string.
Field Options
choices : Sets the choices for this field
default: The default value for the field
help_text: Extra “help” text to be displayed with the form widget. It’s useful for documentation even if your field isn’t used on a form
null: If set to True Django stores empty values as NULL in database, by default it is False.
blank: If True, this field is allowed to be blank, by default its False
primary_key: If True, this field is primary key for model, by default it is False
editable: If False, the field will not be displayed in the admin or any other ModelForm. They are also skipped during model validation. Default is True.
For a live example you can follow this 5 part tutorial series,
part 5: Fluent in Django: Get to know Django models better
EDIT: 2
A number of custom properties are added to the enumeration classes – .choices, .labels, .values, and .names – to make it easier to access lists of those separate parts of the enumeration.
As per django documentation use can the .label property or .name property
TransactionTypes.BUY.label
>>> “Buy” #returns this output as string value
TransactionType.BUY.name
>>> “BUY” # returns this output
TransactionType.BUY.value
>>> 0 # returns this as output
EDIT 3 Based on updated question &comments
Brief information covered in Edit 3
extra instance method example quoted from django 3.2 doc
How to apply extra instance method to your use case
Workaround function to solve issues
Django 3.2 documentation on extra instance method mentions
For every field that has choices set, the object will have a get_FOO_display() method, where FOO is the name of the field. This method returns the “human-readable” value of the field.
Sample example from documentation is given below
from django.db import models
class Person(models.Model):
SHIRT_SIZES = (
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES)
>>>p = Person(name="Fred Flintstone", shirt_size="L")
>>>p.save()
>>>p.shirt_size
‘L’ #output
>>> p.get_shirt_size_display()
‘Large’ #output
APPLYING extra instance method to your use case
Based on your updated question & comments you have mentioned t to be instance of Transactions object and type to be PositiveSmallIntegerField (an instance of TransactionTypes choices)
The t.get_type_display() code should ideally produce the output Buy as string
>>> type= models.PositiveSmallIntegersField(choices=TransactionTypes.choices, null=True, blank=True)
>>> t = Transaction.objects.all().first()
>>> t.type
0 #output
>>> t.get_type_display()
‘Buy’ #output
Workaround
A workaround is to write a separate function that checks with int enum value and return label as string
def label_of_TransactionType:
if (t.type== TransactionType.BUY.value):
return TransactionTypes.BUY.label
else:
return TransactionTypes.SELL.label
I appreciate the consistent definition, but there are other answer for this question is using enums and I think the Enum type is by far the best. They can show an integer and a string for an item at the same time, while keeping your code more readable.see the code snippets below:
app/enums.py
from enum import Enum
class ChoiceEnum(Enum):
def __str__(self):
return self.name
def __int__(self):
return self.value
#classmethod
def choices(cls):
choices = list()
for item in cls: # Loop thru defined enums
choices.append((item.value, item.name))
return tuple(choices)
class TransactionType(ChoiceEnum):
BUY = 0
SELL = 1
# Uh oh
TransactionType.BUY._name_ = 'Buy'
TransactionType.SELL._name_ = 'Sell'
app/models.py
from django.db import models
from myapp.enums import TransactionType
class Transaction(models.Model):
type_of_transaction = models.IntegerField(choices=TransactionType.choices(), default=int(TransactionType.BUY))
# ...
One way is that you define choices as a Tuple of Tuple, each option being the value of the outer tuple.
The first element in each inner tuple is the value to be set in the model, and the second element being its string representation.like the code snippet below:
class Transaction(models.Model):
BUY = 0
SELL = 1
TRANSACTION_TYPE_CHOICES = (
(BUY, 'Buy'),
(SELL, 'Sell'),
)
type_of_transaction = models.IntegerField(
choices=TRANSACTION_TYPE_CHOICES,
default=‌BUY,
)

Overwrite django choices output in graphene

I'm working with graphene and graphene-django and I have a problem with a IntegerField with choices. graphene create an Enum and the output is "A_1" if the value is 1; "A_2" if the value is 2 and so on. Example:
# model
class Foo(models.Model):
score = models.IntegerField(choices=((1, 1), (2, 2), (3, 3), (4, 4), (5, 5)))
# query
query {
foo {
score
}
}
# response
{
"data": {
"foo": {
"source": "A_1"
}
}
}
I found a function that convert the choices values.
def convert_choice_name(name):
name = to_const(force_text(name))
try:
assert_valid_name(name)
except AssertionError:
name = "A_%s" % name
return name
And assert_valid_name has this regular expression:
r'^[_a-zA-Z][_a-zA-Z0-9]*$'
Therefore, whatever begins with number, it will convert it to "A_...".
How I can overwrite this output?
The code comments say
GraphQL serializes Enum values as strings, however internally Enums
can be represented by any kind of type, often integers.
So for your particular case, you're not going to be able to replace the over-the-wire values with integers easily. But it may not matter if the actual value represented by the strings ("A_1") is still an integer internally and on the client end (from the field's description values.)
In general though you can replace the automatically generated field for the field with choices by defining an enum class and adding to the definition of the DjangoObjectType. Here's an example using the documentation Enum example...
class Episode(graphene.Enum):
NEWHOPE = 4
EMPIRE = 5
JEDI = 6
#property
def description(self):
if self == Episode.NEWHOPE:
return 'New Hope Episode'
return 'Other episode'
which you could then add to your DjangoObjectType like
class FooType(DjangoObjectType):
score = Episode()
class Meta:
model = Foo
Or if you want to get extra fancy you can generate the Enum field dynamically from your field's choices in Foo._meta.get_field('score').choices. See graphene_django.converter.convert_django_field_with_choices.
You can set convert_choices_to_enum to False in your Graphene-Django model which will leave them as integers.
class FooType(DjangoObjectType):
class Meta:
model = Foo
convert_choices_to_enum = False
There is more information on the setting here.
Having just bumped into this myself, an alternate is to define your field outside of the Meta (use only_fields) as just a graphene.Int , then you can provide your own resolver function and just return the value of the field, which will end up as a number.
My code snippet (the problem field was resource_type which is the Enum):
class ResourceItem(DjangoObjectType):
class Meta:
model = Resource
only_fields = (
"id",
"title",
"description",
"icon",
"target",
"session_reveal",
"metadata",
"payload",
)
resource_type = graphene.Int()
def resolve_resource_type(self, info):
return self.resource_type

Looking for ForeignKey active and add to QuerySet

class Control(models.Model):
period = models.DurationField()
active = models.BooleanField()
device_collection = models.ForeignKey(DeviceSet)
class DeviceSet(models.Model):
name = models.CharField()
date_last_control = models.DateField()
def get_next_control(self):
return self.date_last_control + self.control_actif.period
#property
def control_actif(self):
if not hasattr(self, "_control"):
setattr(self, "_control", self.control_set.get(active=True))
return self._control
There are several Control associated with DeviceSet but only one Control which is active by DeviceSet.
I'd like to get the active Control of the DeviceSet when I get the queryset in a column _control.
I already try :
DeviceSet.objects.annotate(_control = Q(control__active=True))
That don't work
'WhereNode' object has no attribute 'output_field'
And after set output_field=Control I have the following exception:
type object 'Control' has no attribute 'resolve_expression'
I just want to have like a prefetch_related with filter but in a new column to use the _control attribute in model's method.
You are getting errors from what you've attempted because annotate method needs an aggregate function (eg Sum, Count etc) rather than a Q object.
Since Django 1.7 it's possible to do what you want using prefetch_related, see docs here:
https://docs.djangoproject.com/en/1.8/ref/models/querysets/#django.db.models.Prefetch
DeviceSet.objects.prefetch_related(
Prefetch('control_set',
queryset=Control.objects.filter(active=True),
to_attr='_control')
)

Defining circular references using zope.schema

I'm trying to do the following, define two classes whose instances mutually reference one another, like Users and Groups in the following exemple. A User can belong to several groups and a Group can contains several users. The actual data is stored in a database and there it is a simple matter of many-to-many relationship using foreign keys. No problem at all.
Afterward the data is loaded through an ORM and stored in instances of python objects. Still no problem at all as the ORM used (SQLAlchemy) manage backrefs.
Now I want to check that the python objects comply to some interface using zope.interface and zope.schema. That's where I get into troubles.
import zope.schema as schema
from zope.interface import Interface, implements
class IGroup(Interface):
name = schema.TextLine(title=u"Group's name")
# user_list = schema.List(title = u"List of Users in this group", value_type = sz.Object(IUser))
class IUser(Interface):
name = schema.TextLine(title=u"User's name")
group_list = schema.List(title = u"List of Groups containing that user",
value_type = schema.Object(IGroup))
IGroup._InterfaceClass__attrs['user_list'] = zs.List(title = u"List of Users in this group", required = False, value_type = zs.Object(IUser))
class Group(object):
implements(IGroup)
def __init__(self, name):
self.name = name
self.user_list = []
class User(object):
implements(IUser)
def __init__(self, name):
self.name = name
self.group_list = []
alice = User(u'Alice')
bob = User(u'Bob')
chuck = User(u'Chuck')
group_users = Group(u"Users")
group_auditors = Group(u"Auditors")
group_administrators = Group(u"Administrators")
def add_user_in_group(user, group):
user.group_list.append(group)
group.user_list.append(user)
add_user_in_group(alice, group_users)
add_user_in_group(bob, group_users)
add_user_in_group(chuck, group_users)
add_user_in_group(chuck, group_auditors)
add_user_in_group(chuck, group_administrators)
for x in [alice, bob, chuck]:
errors = schema.getValidationErrors(IUser, x)
if errors: print errors
print "User ", x.name, " is in groups ", [y.name for y in x.group_list]
for x in [group_users, group_auditors, group_administrators]:
errors = schema.getValidationErrors(IGroup, x)
if errors: print errors
print "Group ", x.name, " contains users ", [y.name for y in x.user_list]
My problem is the commented line. I can't define IGroup using IUser because at that time IUser is not yet defined. I've found a workaround completing the definition of IGroup after the definition of IUser but that is not satisfying at all, because IUser and IGroup are defined in different source files and part of IGroup is defined in the file defining IUser.
Is there any proper way to do that using zope.schema ?
Modify the field after definition:
#imports elided
class IFoo(Interface):
bar = schema.Object(schema=Interface)
class IBar(Interface):
foo = schema.Object(schema=IFoo)
IFoo['bar'].schema = IBar
Martijn's answer seems a bit more graceful and self-documenting, but this is a bit more succinct. Neither is perfect (compared to say, Django's solution of using string names for foreign keys) -- pick your poison.
IMHO, it would be nice to specify a dotted name to an interface instead of an identifier. You could pretty easily create a subclass of schema.Object to this end for your own use, should you find that approach useful.
You could define a base, or abstract, interface for IUser:
class IAbstractUser(Interface):
name = schema.TextLine(title=u"User's name")
class IGroup(Interface):
name = schema.TextLine(title=u"Group's name")
user_list = schema.List(
title=u"List of Users in this group",
value_type=schema.Object(IAbstractUser))
class IUser(IAbstractUser):
group_list = schema.List(
title=u"List of Groups containing that user",
value_type=schema.Object(IGroup))
Because IUser is a subclass of IAbstractUser, objects implementing the former also satisfy the latter interface.
Edit: You can always still apply sdupton's dynamic after-the-fact alteration of the IGroup interface after you defined IUser:
IGroup['user_list'].value_type.schema = IUser
I'd still use the Abstract interface pattern to facilitate better code documentation.

Categories