Skip to content

Commit

Permalink
Environment variable to disable plugins (#8767)
Browse files Browse the repository at this point in the history
Co-authored-by: pantero <pantero@panteros-MacBook-Pro.local>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
  • Loading branch information
3 people committed Feb 13, 2024
1 parent 332eac4 commit b145516
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 0 deletions.
9 changes: 9 additions & 0 deletions docs/concepts/plugins.md
Expand Up @@ -116,3 +116,12 @@ class Foo(BaseModel, plugin_settings={'observer': 'all'}):
```

On each validation call, the `plugin_settings` will be passed to a callable registered for the events.

## Disabling Plugins

You can use environment variable `PYDANTIC_DISABLE_PLUGINS` to disable all or specific plugin(s).

| Environment Variable | Allowed Values | Description |
|-------------------------------|-------------------------------------------------------|-------------------------------|
| `PYDANTIC_DISABLE_PLUGINS` | `__all__`, `1`, `true` | Disables all plugins |
| | Comma-separated string (e.g. `my-plugin-1,my-plugin2`)| Disables specified plugin(s) |
6 changes: 6 additions & 0 deletions pydantic/plugin/_loader.py
@@ -1,6 +1,7 @@
from __future__ import annotations

import importlib.metadata as importlib_metadata
import os
import warnings
from typing import TYPE_CHECKING, Final, Iterable

Expand All @@ -22,10 +23,13 @@ def get_plugins() -> Iterable[PydanticPluginProtocol]:
Inspired by: https://github.com/pytest-dev/pluggy/blob/1.3.0/src/pluggy/_manager.py#L376-L402
"""
disabled_plugins = os.getenv('PYDANTIC_DISABLE_PLUGINS')
global _plugins, _loading_plugins
if _loading_plugins:
# this happens when plugins themselves use pydantic, we return no plugins
return ()
elif disabled_plugins in ('__all__', '1', 'true'):
return ()
elif _plugins is None:
_plugins = {}
# set _loading_plugins so any plugins that use pydantic don't themselves use plugins
Expand All @@ -37,6 +41,8 @@ def get_plugins() -> Iterable[PydanticPluginProtocol]:
continue
if entry_point.value in _plugins:
continue
if disabled_plugins is not None and entry_point.name in disabled_plugins.split(','):
continue
try:
_plugins[entry_point.value] = entry_point.load()
except (ImportError, AttributeError) as e:
Expand Down
83 changes: 83 additions & 0 deletions tests/test_plugin_loader.py
@@ -0,0 +1,83 @@
import importlib.metadata as importlib_metadata
import os
from unittest.mock import patch

import pytest

import pydantic.plugin._loader as loader


class EntryPoint:
def __init__(self, name, value, group):
self.name = name
self.value = value
self.group = group

def load(self):
return self.value


class Dist:
entry_points = []

def __init__(self, entry_points):
self.entry_points = entry_points


@pytest.fixture
def reset_plugins():
global loader
initial_plugins = loader._plugins
loader._plugins = None
yield
# teardown
loader._plugins = initial_plugins


@pytest.fixture(autouse=True)
def mock():
mock_entry_1 = EntryPoint(name='test_plugin1', value='test_plugin:plugin1', group='pydantic')
mock_entry_2 = EntryPoint(name='test_plugin2', value='test_plugin:plugin2', group='pydantic')
mock_entry_3 = EntryPoint(name='test_plugin3', value='test_plugin:plugin3', group='pydantic')
mock_dist = Dist([mock_entry_1, mock_entry_2, mock_entry_3])

with patch.object(importlib_metadata, 'distributions', return_value=[mock_dist]):
yield


def test_loader(reset_plugins):
res = loader.get_plugins()
assert list(res) == ['test_plugin:plugin1', 'test_plugin:plugin2', 'test_plugin:plugin3']


def test_disable_all(reset_plugins):
os.environ['PYDANTIC_DISABLE_PLUGINS'] = '__all__'
res = loader.get_plugins()
assert res == ()


def test_disable_all_1(reset_plugins):
os.environ['PYDANTIC_DISABLE_PLUGINS'] = '1'
res = loader.get_plugins()
assert res == ()


def test_disable_true(reset_plugins):
os.environ['PYDANTIC_DISABLE_PLUGINS'] = 'true'
res = loader.get_plugins()
assert res == ()


def test_disable_one(reset_plugins):
os.environ['PYDANTIC_DISABLE_PLUGINS'] = 'test_plugin1'
res = loader.get_plugins()
assert len(list(res)) == 2
assert 'test_plugin:plugin1' not in list(res)


def test_disable_multiple(reset_plugins):
os.environ['PYDANTIC_DISABLE_PLUGINS'] = 'test_plugin1,test_plugin2'
res = loader.get_plugins()
assert len(list(res)) == 1
assert 'test_plugin:plugin1' not in list(res)
assert 'test_plugin:plugin2' not in list(res)

0 comments on commit b145516

Please sign in to comment.