Open k0nG opened 7 years ago
Had some time to think about this :joy:
This week I've wished for a unique generator for strings when working with Factory Boy. I'm not satisfied with using Sequences to guarantee unique values - especially for stringy values which are not meant to contain numbers.
I've started work on uFaker to add functionality to Faker to make it easier to generate unique values and or exclude undesired ones.
Quick recap: factory_djoy
does two things when generating user names for Django Users:
Generates a unique username
using unique_username()
(https://github.com/jamescooke/factory_djoy/blob/master/factory_djoy/factories.py#L108). Using uFaker would mean that this could be reduced to just ufake.user_name()
because uFaker will guarantee uniqueness.
Checks against the existing usernames in the database. If the generated username is already in the db, then a new one is generated.
So, back to the original question:
Would it be possible to provide the Username generator as it's own standalone module/provider?
Do you care about values in the database already?
If no - then would ufake.user_name()
work? My plan is to wire a ufake
instance into factory_djoy
so that unique values can be easily generated across multiple calls to factories.
If yes - then UniqueUsername()
could be made available. My main concern would be that this would not be generic enough to be helpful - for example if the model field name or database column name change.
What I'm thinking of for the "yes I care about existing values in the database" situation is a recipe that uses a ban list:
from factory_djoy import CleanModelFactory, ufake
from project.accounts import Account
class AccountFactory(CleanModelFactory):
class Meta:
model = Account
@lazy_attribute
def username(self):
all_usernames = Account.objects.values_list('username', flat=True)
return ufake.user_name(ban=all_usernames)
What do you think? Would the recipe be powerful enough for what you're looking for?
Either way, my plan is to build out uFaker to the API in its README and then replace the username generation in this library with the recipe above.
I'm so glad that you got to this within 3 years, I was worried we'd go into the 4th year..... 😆
I understand your thinking and I think I do care about existing values in the database sometimes. So a ban list does not sound like a bad idea.
Just a note to self with an example for testing.
Given a Game
model that keeps a single unique value pointing a Server FK on another system:
class Game(models.Model):
server_id = models.IntegerField(unique=True)
code = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now=True)
Then we would have a factory (note the change of import name from the Username
example above - this is so that the API matches Factory Boy):
from factory import Faker
from factory_djoy import CleanModelFactory, uFaker
from games.models import Game
class GameFactory(CleanModelFactory):
class Meta:
model = Game
server_id = uFaker('pyint') # This guarantees unique values for this ID
code = Faker('word') # We don't care about uniqueness here.
This would then mean that a large number of valid Game
instances can be created without the server_id
accidentally colliding and without using Factory Boy's sequence
solution: GameFactory.create_batch(999)
.
It also means that we can easily test what happens when there are missing values without the chance of collision, but maintaining some randomness:
def test_missing():
"""
When no games can be found, serializer is valid, but `_game` is `None`.
"""
GameFactory.create_batch(3, server_id__ban=[29])
serializer = GameSearchSerializer(data={'server_id': 29})
result = serializer.is_valid()
assert result is True, serializer.errors
assert serializer._game is None
When using a custom user model I'd love to still use your unique username generation.
Would it be possible to provide the Username generator as it's own standalone module/provider?
Something like: