Repository: litestar-org/polyfactory
Pull request #628 (2025-01-14 22:17:30)

fix: ensure ABC are not considered a factory type
<!--
By submitting this pull request, you agree to:
- follow [Litestar's Code of Conduct](https://github.com/litestar-org/.github/blob/main/CODE_OF_CONDUCT.md)
- follow [Litestar's contribution guidelines](https://github.com/litestar-org/.github/blob/main/CONTRIBUTING.md)
- follow the [PSFs's Code of Conduct](https://www.python.org/psf/conduct/)
-->
## Description

- `abc.ABC` cannot be instantiated so does not make sense for these to be  considered factory types

<!--
Please add in issue numbers this pull request will close, if applicable
Examples: Fixes #4321 or Closes #1234

Ensure you are using a supported keyword to properly link an issue:
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->
## Closes

- Closes https://github.com/litestar-org/polyfactory/issues/627


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=starlite-api_polyfactory&pullRequest=628) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=628&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=628&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=starlite-api_polyfactory&pullRequest=628&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=628&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=628&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=starlite-api_polyfactory&pullRequest=628)


github-actions[bot]: Documentation preview will be available shortly at https://litestar-org.github.io/polyfactory-docs-preview/628

Patch:
diff --git a/polyfactory/factories/base.py b/polyfactory/factories/base.py
index 72d86dc..a2eaf52 100644
--- a/polyfactory/factories/base.py
+++ b/polyfactory/factories/base.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import copy
+import inspect
 from abc import ABC, abstractmethod
 from collections import Counter, abc, deque
 from contextlib import suppress
@@ -404,7 +405,9 @@ class BaseFactory(ABC, Generic[T]):
         :param annotation: A type annotation.
         :returns: Boolean dictating whether the annotation is a factory type
         """
-        return any(factory.is_supported_type(annotation) for factory in BaseFactory._base_factories)
+        return not inspect.isabstract(annotation) and any(
+            factory.is_supported_type(annotation) for factory in BaseFactory._base_factories
+        )
 
     @classmethod
     def is_batch_factory_type(cls, annotation: Any) -> bool:


Test patch:
diff --git a/tests/models.py b/tests/models.py
index 17cd6dc..a0de034 100644
--- a/tests/models.py
+++ b/tests/models.py
@@ -25,7 +25,7 @@ class Person(BaseModel):
     birthday: Union[datetime, date]
 
 
-class PersonFactoryWithoutDefaults(ModelFactory):
+class PersonFactoryWithoutDefaults(ModelFactory[Person]):
     __model__ = Person
 
 
@@ -39,5 +39,5 @@ class PersonFactoryWithDefaults(PersonFactoryWithoutDefaults):
     birthday = datetime(2021 - 33, 1, 1)
 
 
-class PetFactory(ModelFactory):
+class PetFactory(ModelFactory[Pet]):
     __model__ = Pet
diff --git a/tests/test_base_factories.py b/tests/test_base_factories.py
index 922b4b0..4da6f61 100644
--- a/tests/test_base_factories.py
+++ b/tests/test_base_factories.py
@@ -1,10 +1,12 @@
+from abc import ABC, abstractmethod
 from dataclasses import dataclass
-from typing import Any, Dict
+from typing import Any, Callable, Dict
 
 import pytest
 
 from pydantic.main import BaseModel
 
+from polyfactory.exceptions import ParameterException
 from polyfactory.factories import DataclassFactory
 from polyfactory.factories.base import BaseFactory
 from polyfactory.factories.pydantic_factory import ModelFactory
@@ -96,3 +98,34 @@ def test_create_factory_without_model_reuse_current_factory_model() -> None:
 def test_create_factory_from_base_factory_without_providing_a_model_raises_error() -> None:
     with pytest.raises(TypeError):
         BaseFactory.create_factory()
+
+
+def test_abstract_classes_are_ignored() -> None:
+    @dataclass
+    class Base(ABC):
+        @abstractmethod
+        def f(self) -> int: ...
+
+    @dataclass
+    class Concrete(Base):
+        def f(self) -> int:
+            return 1
+
+    @dataclass
+    class Model:
+        single: Base
+
+    class ModelFactory(DataclassFactory[Model]):
+        @classmethod
+        def get_provider_map(cls) -> Dict[type, Callable[[], Any]]:
+            return {
+                **super().get_provider_map(),
+                Base: Concrete,
+            }
+
+    result = ModelFactory.build()
+    assert isinstance(result, Model)
+    assert isinstance(result.single, Concrete)
+
+    with pytest.raises(ParameterException, match="Unsupported type: "):
+        DataclassFactory.create_factory(Model).build()
diff --git a/tests/test_pydantic_factory.py b/tests/test_pydantic_factory.py
index 2c24db7..03c88e2 100644
--- a/tests/test_pydantic_factory.py
+++ b/tests/test_pydantic_factory.py
@@ -258,7 +258,7 @@ def test_factory_use_construct() -> None:
     # factory should pass values without validation
     invalid_age = "non_valid_age"
     non_validated_pet = PetFactory.build(factory_use_construct=True, age=invalid_age)
-    assert non_validated_pet.age == invalid_age
+    assert non_validated_pet.age == invalid_age  # type: ignore[comparison-overlap]
 
     with pytest.raises(ValidationError):
         PetFactory.build(age=invalid_age)