11from __future__ import annotations
22
3+ import typing
34from abc import ABC , abstractmethod
45from collections import Counter , abc , deque
56from contextlib import suppress
2223from os .path import realpath
2324from pathlib import Path
2425from random import Random
26+
27+ try :
28+ from types import NoneType
29+ except ImportError :
30+ NoneType = type (None ) # type: ignore[misc,assignment]
31+
2532from typing import (
2633 TYPE_CHECKING ,
2734 Any ,
4653 MIN_COLLECTION_LENGTH ,
4754 RANDOMIZE_COLLECTION_LENGTH ,
4855)
49- from polyfactory .exceptions import (
50- ConfigurationException ,
51- MissingBuildKwargException ,
52- ParameterException ,
53- )
56+ from polyfactory .exceptions import ConfigurationException , MissingBuildKwargException , ParameterException
5457from polyfactory .fields import Fixture , Ignore , PostGenerated , Require , Use
55- from polyfactory .utils .helpers import get_collection_type , unwrap_annotation , unwrap_args , unwrap_optional
58+ from polyfactory .utils .helpers import (
59+ flatten_annotation ,
60+ get_collection_type ,
61+ unwrap_annotation ,
62+ unwrap_args ,
63+ unwrap_optional ,
64+ )
65+ from polyfactory .utils .model_coverage import CoverageContainer , CoverageContainerCallable , resolve_kwargs_coverage
5666from polyfactory .utils .predicates import (
5767 get_type_origin ,
5868 is_any ,
6171 is_safe_subclass ,
6272 is_union ,
6373)
64- from polyfactory .value_generators .complex_types import handle_collection_type
74+ from polyfactory .value_generators .complex_types import handle_collection_type , handle_collection_type_coverage
6575from polyfactory .value_generators .constrained_collections import (
6676 handle_constrained_collection ,
6777 handle_constrained_mapping ,
@@ -263,6 +273,32 @@ def _handle_factory_field(cls, field_value: Any, field_build_parameters: Any | N
263273
264274 return field_value () if callable (field_value ) else field_value
265275
276+ @classmethod
277+ def _handle_factory_field_coverage (cls , field_value : Any , field_build_parameters : Any | None = None ) -> Any :
278+ """Handle a value defined on the factory class itself.
279+
280+ :param field_value: A value defined as an attribute on the factory class.
281+ :param field_build_parameters: Any build parameters passed to the factory as kwarg values.
282+
283+ :returns: An arbitrary value correlating with the given field_meta value.
284+ """
285+ if is_safe_subclass (field_value , BaseFactory ):
286+ if isinstance (field_build_parameters , Mapping ):
287+ return CoverageContainer (field_value .coverage (** field_build_parameters ))
288+
289+ if isinstance (field_build_parameters , Sequence ):
290+ return [CoverageContainer (field_value .coverage (** parameter )) for parameter in field_build_parameters ]
291+
292+ return CoverageContainer (field_value .coverage ())
293+
294+ if isinstance (field_value , Use ):
295+ return field_value .to_value ()
296+
297+ if isinstance (field_value , Fixture ):
298+ return CoverageContainerCallable (field_value .to_value )
299+
300+ return CoverageContainerCallable (field_value ) if callable (field_value ) else field_value
301+
266302 @classmethod
267303 def _get_or_create_factory (cls , model : type ) -> type [BaseFactory [Any ]]:
268304 """Get a factory from registered factories or generate a factory dynamically.
@@ -635,6 +671,66 @@ def get_field_value( # noqa: C901, PLR0911, PLR0912
635671 msg ,
636672 )
637673
674+ @classmethod
675+ def get_field_value_coverage ( # noqa: C901
676+ cls ,
677+ field_meta : FieldMeta ,
678+ field_build_parameters : Any | None = None ,
679+ ) -> typing .Iterable [Any ]:
680+ """Return a field value on the subclass if existing, otherwise returns a mock value.
681+
682+ :param field_meta: FieldMeta instance.
683+ :param field_build_parameters: Any build parameters passed to the factory as kwarg values.
684+
685+ :returns: An iterable of values.
686+
687+ """
688+ if cls .is_ignored_type (field_meta .annotation ):
689+ return [None ]
690+
691+ for unwrapped_annotation in flatten_annotation (field_meta .annotation ):
692+ if unwrapped_annotation in (None , NoneType ):
693+ yield None
694+
695+ elif is_literal (annotation = unwrapped_annotation ) and (literal_args := get_args (unwrapped_annotation )):
696+ yield CoverageContainer (literal_args )
697+
698+ elif isinstance (unwrapped_annotation , EnumMeta ):
699+ yield CoverageContainer (list (unwrapped_annotation ))
700+
701+ elif field_meta .constraints :
702+ yield CoverageContainerCallable (
703+ cls .get_constrained_field_value ,
704+ annotation = unwrapped_annotation ,
705+ field_meta = field_meta ,
706+ )
707+
708+ elif BaseFactory .is_factory_type (annotation = unwrapped_annotation ):
709+ yield CoverageContainer (
710+ cls ._get_or_create_factory (model = unwrapped_annotation ).coverage (
711+ ** (field_build_parameters if isinstance (field_build_parameters , Mapping ) else {}),
712+ ),
713+ )
714+
715+ elif (origin := get_type_origin (unwrapped_annotation )) and issubclass (origin , Collection ):
716+ yield handle_collection_type_coverage (field_meta , origin , cls )
717+
718+ elif is_any (unwrapped_annotation ) or isinstance (unwrapped_annotation , TypeVar ):
719+ yield create_random_string (cls .__random__ , min_length = 1 , max_length = 10 )
720+
721+ elif provider := cls .get_provider_map ().get (unwrapped_annotation ):
722+ yield CoverageContainerCallable (provider )
723+
724+ elif callable (unwrapped_annotation ):
725+ # if value is a callable we can try to naively call it.
726+ # this will work for callables that do not require any parameters passed
727+ yield CoverageContainerCallable (unwrapped_annotation )
728+ else :
729+ msg = f"Unsupported type: { unwrapped_annotation !r} \n \n Either extend the providers map or add a factory function for this type."
730+ raise ParameterException (
731+ msg ,
732+ )
733+
638734 @classmethod
639735 def should_set_none_value (cls , field_meta : FieldMeta ) -> bool :
640736 """Determine whether a given model field_meta should be set to None.
@@ -752,6 +848,50 @@ def process_kwargs(cls, **kwargs: Any) -> dict[str, Any]:
752848
753849 return result
754850
851+ @classmethod
852+ def process_kwargs_coverage (cls , ** kwargs : Any ) -> abc .Iterable [dict [str , Any ]]:
853+ """Process the given kwargs and generate values for the factory's model.
854+
855+ :param kwargs: Any build kwargs.
856+
857+ :returns: A dictionary of build results.
858+
859+ """
860+ result : dict [str , Any ] = {** kwargs }
861+ generate_post : dict [str , PostGenerated ] = {}
862+
863+ for field_meta in cls .get_model_fields ():
864+ field_build_parameters = cls .extract_field_build_parameters (field_meta = field_meta , build_args = kwargs )
865+
866+ if cls .should_set_field_value (field_meta , ** kwargs ):
867+ if hasattr (cls , field_meta .name ) and not hasattr (BaseFactory , field_meta .name ):
868+ field_value = getattr (cls , field_meta .name )
869+ if isinstance (field_value , Ignore ):
870+ continue
871+
872+ if isinstance (field_value , Require ) and field_meta .name not in kwargs :
873+ msg = f"Require kwarg { field_meta .name } is missing"
874+ raise MissingBuildKwargException (msg )
875+
876+ if isinstance (field_value , PostGenerated ):
877+ generate_post [field_meta .name ] = field_value
878+ continue
879+
880+ result [field_meta .name ] = cls ._handle_factory_field_coverage (
881+ field_value = field_value ,
882+ field_build_parameters = field_build_parameters ,
883+ )
884+ continue
885+
886+ result [field_meta .name ] = CoverageContainer (
887+ cls .get_field_value_coverage (field_meta , field_build_parameters = field_build_parameters ),
888+ )
889+
890+ for resolved in resolve_kwargs_coverage (result ):
891+ for field_name , post_generator in generate_post .items ():
892+ resolved [field_name ] = post_generator .to_value (field_name , resolved )
893+ yield resolved
894+
755895 @classmethod
756896 def build (cls , ** kwargs : Any ) -> T :
757897 """Build an instance of the factory's __model__
@@ -776,6 +916,19 @@ def batch(cls, size: int, **kwargs: Any) -> list[T]:
776916 """
777917 return [cls .build (** kwargs ) for _ in range (size )]
778918
919+ @classmethod
920+ def coverage (cls , ** kwargs : Any ) -> abc .Iterator [T ]:
921+ """Build a batch of the factory's Meta.model will full coverage of the sub-types of the model.
922+
923+ :param kwargs: Any kwargs. If field_meta names are set in kwargs, their values will be used.
924+
925+ :returns: A iterator of instances of type T.
926+
927+ """
928+ for data in cls .process_kwargs_coverage (** kwargs ):
929+ instance = cls .__model__ (** data )
930+ yield cast ("T" , instance )
931+
779932 @classmethod
780933 def create_sync (cls , ** kwargs : Any ) -> T :
781934 """Build and persists synchronously a single model instance.
0 commit comments