Skip to content

# `nautobot.apps.factory`

Nautobot classes and utilities for factory boy.

## `nautobot.apps.factory.BaseModelFactory`

Bases: `DjangoModelFactory`

Base class for all Nautobot model factories.

Source code in `nautobot/core/factory.py`

    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    class BaseModelFactory(DjangoModelFactory):
        """Base class for all Nautobot model factories."""

        id = UniqueFaker("uuid4")

        @classmethod
        def _create(cls, model_class, *args, **kwargs):
            """Override default DjangoModelFactory behavior to call validated_save() instead of just save()."""
            using = kwargs.pop("using", cls._meta.database)
            obj = model_class(*args, **kwargs)
            obj.validated_save(using=using)
            return obj

## `nautobot.apps.factory.NautobotBoolIterator`

Bases: `factory.Iterator`

Factory iterator that returns a semi-random sampling of boolean values

Iterator that returns a random sampling of True and False values while limiting the number of repeated values in a given number of iterations. Used in factories when a data set must contain both True and False values.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`cycle` | `boolean` | If True, iterator will restart at the beginning when all values are exhausted. Otherwise raise a `StopIterator` exception when values are exhausted. Defaults to True. | `True`
`chance_of_getting_true` | `int` | Percentage (0-100) of the values in the returned iterable set to True. Defaults to 50. | `constants.NAUTOBOT_BOOL_ITERATOR_DEFAULT_PROBABILITY`
`length` | `int` | Length of the returned iterable. Defaults to 8. | `constants.NAUTOBOT_BOOL_ITERATOR_DEFAULT_LENGTH`

Source code in `nautobot/core/factory.py`

    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    class NautobotBoolIterator(factory.Iterator):
        """Factory iterator that returns a semi-random sampling of boolean values

        Iterator that returns a random sampling of True and False values while limiting
        the number of repeated values in a given number of iterations. Used in factories
        when a data set must contain both True and False values.

        Args:
            cycle (boolean): If True, iterator will restart at the beginning when all values are
                exhausted. Otherwise raise a `StopIterator` exception when values are exhausted.
                Defaults to True.
            chance_of_getting_true (int): Percentage (0-100) of the values in the returned iterable
                set to True. Defaults to 50.
            length (int): Length of the returned iterable. Defaults to 8.
        """

        def _nautobot_boolean_iterator_sample(self, chance_of_getting_true, length):
            iterator = list(itertools.repeat(True, int(length * chance_of_getting_true / 100)))
            iterator += list(itertools.repeat(False, length - len(iterator)))
            factory.random.randgen.shuffle(iterator)
            return iterator

        def __init__(
            self,
            *args,
            cycle=True,
            getter=None,
            chance_of_getting_true=constants.NAUTOBOT_BOOL_ITERATOR_DEFAULT_PROBABILITY,
            length=constants.NAUTOBOT_BOOL_ITERATOR_DEFAULT_LENGTH,
        ):
            super().__init__(None, cycle=cycle, getter=getter)

            if cycle:
                self.iterator_builder = lambda: factory.utils.ResetableIterator(
                    itertools.cycle(self._nautobot_boolean_iterator_sample(chance_of_getting_true, length))
                )
            else:
                self.iterator_builder = lambda: factory.utils.ResetableIterator(
                    self._nautobot_boolean_iterator_sample(chance_of_getting_true, length)
                )

## `nautobot.apps.factory.NautobotFakerProvider`

Bases: `BaseProvider`

Faker provider to generate fake data specific to Nautobot or network automation use cases.

Source code in `nautobot/core/factory.py`

    212
    213
    214
    215
    216
    217
    218
    219
    220
    class NautobotFakerProvider(BaseProvider):
        """Faker provider to generate fake data specific to Nautobot or network automation use cases."""

        def ipv6_network(self) -> str:
            """Produce a random IPv6 network with a valid CIDR greater than 0"""
            address = str(IPv6Address(self.generator.random.randint(0, (2**IPV6LENGTH) - 1)))
            address += "/" + str(self.generator.random.randint(1, IPV6LENGTH))
            address = str(IPv6Network(address, strict=False))
            return address

### `ipv6_network()`

Produce a random IPv6 network with a valid CIDR greater than 0

Source code in `nautobot/core/factory.py`

    215
    216
    217
    218
    219
    220
    def ipv6_network(self) -> str:
        """Produce a random IPv6 network with a valid CIDR greater than 0"""
        address = str(IPv6Address(self.generator.random.randint(0, (2**IPV6LENGTH) - 1)))
        address += "/" + str(self.generator.random.randint(1, IPV6LENGTH))
        address = str(IPv6Network(address, strict=False))
        return address

## `nautobot.apps.factory.OrganizationalModelFactory`

Bases: `BaseModelFactory`

Factory base class for OrganizationalModel subclasses.

Source code in `nautobot/core/factory.py`

    40
    41
    class OrganizationalModelFactory(BaseModelFactory):
        """Factory base class for OrganizationalModel subclasses."""

## `nautobot.apps.factory.PrimaryModelFactory`

Bases: `BaseModelFactory`

Factory base class for PrimaryModel subclasses.

Source code in `nautobot/core/factory.py`

    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    class PrimaryModelFactory(BaseModelFactory):
        """Factory base class for PrimaryModel subclasses."""

        # TODO random created/last_updated values?
        # TODO random custom_field data?
        # TODO random relationships?
        # TODO random dynamic-groups?
        # TODO random notes?

        @factory.post_generation
        def tags(self, create, extracted, **kwargs):
            if create:
                if extracted:
                    self.tags.set(extracted)
                else:
                    self.tags.set(get_random_instances(Tag.objects.get_for_model(self._meta.model)))

## `nautobot.apps.factory.UniqueFaker`

Bases: `factory.Faker`

https://github.com/FactoryBoy/factory_boy/pull/820#issuecomment-1004802669

Source code in `nautobot/core/factory.py`

    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class UniqueFaker(factory.Faker):
        """https://github.com/FactoryBoy/factory_boy/pull/820#issuecomment-1004802669"""

        @classmethod
        def _get_faker(cls, locale=None):
            return super()._get_faker(locale=locale).unique

        def clear(self, locale=None):
            subfaker = self._get_faker(locale)
            subfaker.clear()

## `nautobot.apps.factory.get_random_instances(model_or_queryset_or_lambda, minimum=0, maximum=None)`

Factory helper - retrieve a random number of instances of the given model.

This is different from random_instance() in that it's not itself a lazy function generator, but should instead be called only from within a @lazy_attribute or @post_generation function.

This is not an evenly weighted distribution (all counts equally likely), because in most of our code, the relevant code paths distinguish between 0, 1, or >1 instances - there's not a functional difference between "2 instances" and "10 instances" in most cases. Therefore, this implementation provides: - 1/3 chance of no instances - 1/3 chance of 1 instance - 1/3 chance of (2 to n) instances, where each possibility is equally likely within this range

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`model_or_queryset_or_lambda` | `Union[BaseModel, QuerySet, func]` | Either a model class, a model queryset, or a lambda that returns one of those | required
`minimum` | `int` | Minimum number of objects to return | `0`
`maximum` | `int` | Maximum number of objects to return, or None for no limit | `None`

Source code in `nautobot/core/factory.py`

    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    def get_random_instances(model_or_queryset_or_lambda, minimum=0, maximum=None):
        """
        Factory helper - retrieve a random number of instances of the given model.

        This is different from random_instance() in that it's not itself a lazy function generator, but should instead be
        called only from within a @lazy_attribute or @post_generation function.

        This is not an evenly weighted distribution (all counts equally likely), because in most of our code,
        the relevant code paths distinguish between 0, 1, or >1 instances - there's not a functional difference between
        "2 instances" and "10 instances" in most cases. Therefore, this implementation provides:
            - 1/3 chance of no instances
            - 1/3 chance of 1 instance
            - 1/3 chance of (2 to n) instances, where each possibility is equally likely within this range

        Args:
            model_or_queryset_or_lambda (Union[BaseModel, QuerySet, func]): Either a model class, a model queryset, or a lambda that returns one of those
            minimum (int): Minimum number of objects to return
            maximum (int): Maximum number of objects to return, or None for no limit
        """
        branch = factory.random.randgen.randint(0, 2)
        queryset = _get_queryset_from_model_or_queryset_or_lambda(model_or_queryset_or_lambda)
        count = queryset.count()
        if maximum is None:
            maximum = count
        if any([branch == 0 and minimum == 0, count == 0, maximum == 0]):
            return []
        if any([branch == 1 and minimum <= 1, count == 1, maximum == 1]):
            return [factory.random.randgen.choice(queryset)]
        return factory.random.randgen.sample(
            population=list(queryset),
            k=factory.random.randgen.randint(max(2, minimum), min(maximum, count)),
        )

## `nautobot.apps.factory.random_instance(model_or_queryset_or_lambda, allow_null=True)`

Factory helper - construct a LazyFunction that gets a random instance of the given model or queryset when evaluated.

TODO: once we have factories for all mandatory foreign keys, change allow_null default to False

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`model_or_queryset_or_lambda` | `Union[BaseModel, QuerySet, func]` | Either a model class, a model queryset, or a lambda that returns one of those | required
`allow_null` | `bool` | If False, and the given queryset contains no objects, raise a RuntimeError. | `True`

Example

class ObjectFactory(DjangoModelFactory): class Meta: model = Object exclude = ("has_group,")

    # Required foreign key
    user = random_instance(User, allow_null=False)

    # Optional foreign key
    has_group = NautobotBoolIterator()
    group = factory.Maybe("has_group", random_instance(Group), None)

    # Foreign key selected from a filtered queryset
    tenant = random_instance(Tenant.objects.filter(group__isnull=False))

    # Foreign key selected from a queryset generated by a lambda
    # This needs to be done this way because .get_for_model() evaluates a ContentType queryset,
    # and we need to defer evaluation of that queryset as well.
    status = random_instance(lambda: Status.objects.get_for_model(Object), allow_null=False)

Source code in `nautobot/core/factory.py`

     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    def random_instance(model_or_queryset_or_lambda, allow_null=True):
        """
        Factory helper - construct a LazyFunction that gets a random instance of the given model or queryset when evaluated.

        TODO: once we have factories for all mandatory foreign keys, change allow_null default to False

        Args:
            model_or_queryset_or_lambda (Union[BaseModel, QuerySet, func]): Either a model class, a model queryset, or a lambda that returns one of those
            allow_null (bool): If False, and the given queryset contains no objects, raise a RuntimeError.

        Example:
            class ObjectFactory(DjangoModelFactory):
                class Meta:
                    model = Object
                    exclude = ("has_group,")

                # Required foreign key
                user = random_instance(User, allow_null=False)

                # Optional foreign key
                has_group = NautobotBoolIterator()
                group = factory.Maybe("has_group", random_instance(Group), None)

                # Foreign key selected from a filtered queryset
                tenant = random_instance(Tenant.objects.filter(group__isnull=False))

                # Foreign key selected from a queryset generated by a lambda
                # This needs to be done this way because .get_for_model() evaluates a ContentType queryset,
                # and we need to defer evaluation of that queryset as well.
                status = random_instance(lambda: Status.objects.get_for_model(Object), allow_null=False)
        """

        def get_random_instance():
            queryset = _get_queryset_from_model_or_queryset_or_lambda(model_or_queryset_or_lambda)

            if not allow_null and not queryset.exists():
                raise RuntimeError(f"No objects in queryset for {model_or_queryset_or_lambda}! {queryset.explain()}")

            return factory.random.randgen.choice(queryset) if queryset.exists() else None

        return factory.LazyFunction(get_random_instance)