Skip to content

Commit

Permalink
revert plugin performance regression (#7589)
Browse files Browse the repository at this point in the history
Co-authored-by: Serge Matveenko <lig@pydantic.dev>
  • Loading branch information
samuelcolvin and lig committed Sep 25, 2023
1 parent 0c760c0 commit 0af0cc3
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 23 deletions.
47 changes: 29 additions & 18 deletions pydantic/plugin/_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,41 @@

PYDANTIC_ENTRY_POINT_GROUP: Final[str] = 'pydantic'

_plugins: dict[str, PydanticPluginProtocol] = {}
# cache of plugins
_plugins: dict[str, PydanticPluginProtocol] | None = None
# return no plugins while loading plugins to avoid recursion and errors while import plugins
# this means that if plugins use pydantic
_loading_plugins: bool = False


def get_plugins() -> Iterable[PydanticPluginProtocol]:
"""Load plugins for Pydantic.
Inspired by: https://github.com/pytest-dev/pluggy/blob/1.3.0/src/pluggy/_manager.py#L376-L402
"""
global _plugins

for dist in importlib_metadata.distributions():
for entry_point in dist.entry_points:
if entry_point.group != PYDANTIC_ENTRY_POINT_GROUP:
continue
if entry_point.value in _plugins:
continue
try:
_plugins[entry_point.value] = entry_point.load()
except (ImportError, AttributeError) as e:
error_type = e.__class__.__name__
warnings.warn(
f'{error_type} while loading the `{entry_point.name}` Pydantic plugin, this could be caused '
f'by a circular import issue (e.g. the plugin importing Pydantic), Pydantic will attempt to '
f'import this plugin again each time a Pydantic validators is created. {e}'
)
global _plugins, _loading_plugins
if _loading_plugins:
# this happens when plugins themselves use pydantic, we return no plugins
return ()
elif _plugins is None:
_plugins = {}
# set _loading_plugins so any plugins that use pydantic don't themselves use plugins
_loading_plugins = True
try:
for dist in importlib_metadata.distributions():
for entry_point in dist.entry_points:
if entry_point.group != PYDANTIC_ENTRY_POINT_GROUP:
continue
if entry_point.value in _plugins:
continue
try:
_plugins[entry_point.value] = entry_point.load()
except (ImportError, AttributeError) as e:
warnings.warn(
f'{e.__class__.__name__} while loading the `{entry_point.name}` Pydantic plugin, '
f'this plugin will not be installed.\n\n{e!r}'
)
finally:
_loading_plugins = False

return _plugins.values()
4 changes: 4 additions & 0 deletions tests/plugin/example_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ class MyModel(BaseModel):
x: int


m = MyModel(x='10')
if m.x != 10:
raise ValueError('m.x should be 10')

log = []


Expand Down
8 changes: 3 additions & 5 deletions tests/plugin/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
def test_plugin_usage():
from pydantic import BaseModel

with pytest.warns(UserWarning, match='AttributeError while loading the `my_plugin` Pydantic plugin.*'):

class MyModel(BaseModel):
x: int
y: str
class MyModel(BaseModel):
x: int
y: str

m = MyModel(x='10', y='hello')
assert m.x == 10
Expand Down

0 comments on commit 0af0cc3

Please sign in to comment.