Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
tarator
Funkwhale
Commits
612dc8ee
Unverified
Commit
612dc8ee
authored
5 years ago
by
Eliot Berriot
Browse files
Options
Download
Email Patches
Plain Diff
Plugins WIP
parent
8f261f96
plugins
No related merge requests found
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
api/funkwhale_api/common/apps.py
+62
-0
api/funkwhale_api/common/apps.py
api/funkwhale_api/common/plugins.py
+111
-0
api/funkwhale_api/common/plugins.py
api/tests/common/test_plugins.py
+106
-0
api/tests/common/test_plugins.py
api/tests/conftest.py
+10
-0
api/tests/conftest.py
with
289 additions
and
0 deletions
+289
-0
api/funkwhale_api/common/apps.py
+
62
-
0
View file @
612dc8ee
"""
Ideal API:
# myplugin/apps.py
from funkwhale_api import plugins
class Plugin(plugins.Plugin):
name = 'scrobbler'
config_options = [
{
'id': 'user_agent',
'verbose_name': 'User agent string',
'help_text': 'The user agent string used by this plugin for external HTTP request',
'default': None,
},
{
'id': 'timeout',
'type': 'int',
'verbose_name': 'Timeout (in seconds)'
'help_text': 'Max timeout for HTTP calls',
'default': 10,
},
]
def get_user_options(self):
from . import options
return [
options.ListenBrainz,
options.LastFm,
]
# myplugin/hooks.py
from .apps import Plugin
@Plugin.register_action('history.listening_created')
def scrobble(plugin, user, listening, **kwargs):
user_options = plugin.get_options(user)
if len(options) == 0:
return
for option in user_options:
if option.id == 'listenbrainz':
broadcast_to_listenbrainz()
"""
from
django.apps
import
AppConfig
,
apps
from
.
import
mutations
from
.
import
plugins
class
CommonConfig
(
AppConfig
):
...
...
@@ -11,3 +64,12 @@ class CommonConfig(AppConfig):
app_names
=
[
app
.
name
for
app
in
apps
.
app_configs
.
values
()]
mutations
.
registry
.
autodiscover
(
app_names
)
plugins
.
init
(
plugins
.
registry
,
[
app
for
app
in
apps
.
app_configs
.
values
()
if
getattr
(
app
,
"_is_funkwhale_plugin"
,
False
)
is
True
],
)
This diff is collapsed.
Click to expand it.
api/funkwhale_api/common/plugins.py
0 → 100644
+
111
-
0
View file @
612dc8ee
import
logging
import
persisting_theory
from
django.apps
import
AppConfig
logger
=
logging
.
getLogger
(
"funkwhale.plugins"
)
class
PluginsRegistry
(
persisting_theory
.
Registry
):
look_into
=
"hooks"
def
prepare_name
(
self
,
data
,
name
=
None
):
return
data
.
name
def
prepare_data
(
self
,
data
):
data
.
plugins_registry
=
self
return
data
def
dispatch_action
(
self
,
action_name
,
**
kwargs
):
logger
.
debug
(
"Dispatching plugin action %s"
,
action_name
)
for
plugin
in
self
.
values
():
try
:
handler
=
plugin
.
hooked_actions
[
action_name
]
except
KeyError
:
continue
logger
.
debug
(
"Hook found for plugin %s"
,
plugin
.
name
)
try
:
handler
(
plugin
=
plugin
,
**
kwargs
)
except
Exception
:
logger
.
exception
(
"Hook for action %s from plugin %s failed. The plugin may be misconfigured."
,
action_name
,
plugin
.
name
,
)
else
:
logger
.
info
(
"Hook for action %s from plugin %s successful"
,
action_name
,
plugin
.
name
,
)
registry
=
PluginsRegistry
()
class
Plugin
(
AppConfig
):
_is_funkwhale_plugin
=
True
is_initialized
=
False
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
().
__init__
(
*
args
,
**
kwargs
)
self
.
config
=
{}
self
.
hooked_actions
=
{}
def
get_config
(
self
,
config
):
"""
Called with config options extracted from env vars, if any specified
Returns a transformed dict
"""
return
config
def
set_config
(
self
,
config
):
"""
Simply persist the given config on the plugin
"""
self
.
config
=
config
def
initialize
(
self
):
pass
def
register_action
(
self
,
action_name
,
func
):
logger
.
debug
(
"Registered hook for action %s via plugin %s"
,
action_name
,
self
.
name
)
self
.
hooked_actions
[
action_name
]
=
func
def
init
(
registry
,
plugins
):
logger
.
debug
(
"Initializing plugins..."
)
for
plugin
in
plugins
:
logger
.
info
(
"Initializing plugin %s"
,
plugin
.
name
)
try
:
config
=
plugin
.
get_config
({})
except
Exception
:
logger
.
exception
(
"Error while getting configuration, plugin %s disabled"
,
plugin
.
name
)
continue
try
:
plugin
.
set_config
(
config
)
except
Exception
:
logger
.
exception
(
"Error while setting configuration, plugin %s disabled"
,
plugin
.
name
)
continue
try
:
plugin
.
initialize
()
except
Exception
:
logger
.
exception
(
"Error while initializing, plugin %s disabled"
,
plugin
.
name
)
continue
plugin
.
is_initialized
=
True
# initialization complete, now we can log the "hooks.py" file in each
# plugin directory
registry
.
autodiscover
([
p
.
name
for
p
in
plugins
if
p
.
is_initialized
])
This diff is collapsed.
Click to expand it.
api/tests/common/test_plugins.py
0 → 100644
+
106
-
0
View file @
612dc8ee
from
funkwhale_api.common
import
plugins
# setup code to populate plugins registry
# plugin-to-user -> enable and configure
# plugin preferences
def
test_plugin_register
(
plugins_registry
):
class
TestPlugin
(
plugins
.
Plugin
):
name
=
"scrobbler"
verbose_name
=
"Audio Scrobbler"
inst
=
TestPlugin
(
app_name
=
"scrobbler"
,
app_module
=
""
)
plugins_registry
.
register
(
inst
)
assert
inst
.
plugins_registry
==
plugins_registry
assert
inst
.
is_initialized
is
False
assert
plugins_registry
[
"scrobbler"
]
==
inst
assert
inst
.
config
==
{}
def
test_plugin_get_config
(
plugins_registry
):
class
TestPlugin
(
plugins
.
Plugin
):
name
=
"scrobbler"
verbose_name
=
"Audio Scrobbler"
plugin
=
TestPlugin
(
app_name
=
""
,
app_module
=
""
)
assert
plugin
.
get_config
({
"hello"
:
"world"
})
==
{
"hello"
:
"world"
}
def
test_plugin_set_config
(
plugins_registry
):
class
TestPlugin
(
plugins
.
Plugin
):
name
=
"scrobbler"
verbose_name
=
"Audio Scrobbler"
plugin
=
TestPlugin
(
app_name
=
""
,
app_module
=
""
)
plugin
.
set_config
({
"hello"
:
"world"
})
assert
plugin
.
config
==
{
"hello"
:
"world"
}
def
test_plugin_initialize
(
plugins_registry
):
class
TestPlugin
(
plugins
.
Plugin
):
name
=
"scrobbler"
verbose_name
=
"Audio Scrobbler"
plugin
=
TestPlugin
(
app_name
=
""
,
app_module
=
""
)
assert
plugin
.
initialize
()
is
None
def
test_action
(
mocker
,
plugins_registry
):
class
TestPlugin
(
plugins
.
Plugin
):
name
=
"scrobbler"
verbose_name
=
"Audio Scrobbler"
inst
=
TestPlugin
(
app_name
=
"scrobbler"
,
app_module
=
""
)
plugins_registry
.
register
(
inst
)
stub
=
mocker
.
stub
()
# nothing hooked, so stub is not called
plugins_registry
.
dispatch_action
(
"hello"
,
user
=
"test"
,
arg1
=
"value1"
,
arg2
=
"value2"
)
stub
.
assert_not_called
()
# now we hook the stub on the action
inst
.
register_action
(
"hello"
,
stub
)
assert
inst
.
hooked_actions
==
{
"hello"
:
stub
}
plugins_registry
.
dispatch_action
(
"hello"
,
user
=
"test"
,
arg1
=
"value1"
,
arg2
=
"value2"
)
stub
.
assert_called_once_with
(
plugin
=
inst
,
user
=
"test"
,
arg1
=
"value1"
,
arg2
=
"value2"
)
def
test_plugins_init
(
plugins_registry
,
mocker
):
class
TestPlugin1
(
plugins
.
Plugin
):
name
=
"scrobbler"
verbose_name
=
"Audio Scrobbler"
class
TestPlugin2
(
plugins
.
Plugin
):
name
=
"webhooks"
verbose_name
=
"Webhooks"
plugin1
=
TestPlugin1
(
app_name
=
"scrobbler"
,
app_module
=
""
)
plugin2
=
TestPlugin2
(
app_name
=
"webhooks"
,
app_module
=
""
)
mocks
=
{}
for
plugin
in
[
plugin1
,
plugin2
]:
d
=
{
"get_config"
:
mocker
.
patch
.
object
(
plugin
,
"get_config"
),
"set_config"
:
mocker
.
patch
.
object
(
plugin
,
"set_config"
),
"initialize"
:
mocker
.
patch
.
object
(
plugin
,
"initialize"
),
}
mocks
[
plugin
.
name
]
=
d
autodiscover
=
mocker
.
patch
.
object
(
plugins_registry
,
"autodiscover"
)
plugins
.
init
(
plugins_registry
,
[
plugin1
,
plugin2
])
autodiscover
.
assert_called_once_with
([
plugin1
.
name
,
plugin2
.
name
])
for
mock_conf
in
mocks
.
values
():
mock_conf
[
"get_config"
].
assert_called_once_with
({})
mock_conf
[
"set_config"
].
assert_called_once_with
(
mock_conf
[
"get_config"
].
return_value
)
mock_conf
[
"initialize"
].
assert_called_once_with
()
assert
plugin1
.
is_initialized
is
True
assert
plugin2
.
is_initialized
is
True
This diff is collapsed.
Click to expand it.
api/tests/conftest.py
+
10
-
0
View file @
612dc8ee
...
...
@@ -28,6 +28,7 @@ from rest_framework import fields as rest_fields
from
rest_framework.test
import
APIClient
,
APIRequestFactory
from
funkwhale_api.activity
import
record
from
funkwhale_api.common
import
plugins
from
funkwhale_api.federation
import
actors
from
funkwhale_api.moderation
import
mrf
...
...
@@ -437,3 +438,12 @@ def mrf_outbox_registry(mocker):
registry
=
mrf
.
Registry
()
mocker
.
patch
(
"funkwhale_api.moderation.mrf.outbox"
,
registry
)
return
registry
@
pytest
.
fixture
def
plugins_registry
(
mocker
):
mocker
.
patch
.
dict
(
plugins
.
registry
,
{})
mocker
.
patch
.
object
(
plugins
.
Plugin
,
"_path_from_module"
,
return_value
=
"/tmp/dummypath"
)
return
plugins
.
registry
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment
Menu
Projects
Groups
Snippets
Help