mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-17 23:13:00 +00:00
Merge pull request #2203 from minrk/entrypoints
allow spawners and authenticators to register via entry points
This commit is contained in:
@@ -75,7 +75,6 @@ Writing an Authenticator that looks up passwords in a dictionary
|
||||
requires only overriding this one method:
|
||||
|
||||
```python
|
||||
from tornado import gen
|
||||
from IPython.utils.traitlets import Dict
|
||||
from jupyterhub.auth import Authenticator
|
||||
|
||||
@@ -85,8 +84,7 @@ class DictionaryAuthenticator(Authenticator):
|
||||
help="""dict of username:password for authentication"""
|
||||
)
|
||||
|
||||
@gen.coroutine
|
||||
def authenticate(self, handler, data):
|
||||
async def authenticate(self, handler, data):
|
||||
if self.passwords.get(data['username']) == data['password']:
|
||||
return data['username']
|
||||
```
|
||||
@@ -143,6 +141,41 @@ See a list of custom Authenticators [on the wiki](https://github.com/jupyterhub/
|
||||
If you are interested in writing a custom authenticator, you can read
|
||||
[this tutorial](http://jupyterhub-tutorial.readthedocs.io/en/latest/authenticators.html).
|
||||
|
||||
### Registering custom Authenticators via entry points
|
||||
|
||||
As of JupyterHub 1.0, custom authenticators can register themselves via
|
||||
the `jupyterhub.authenticators` entry point metadata.
|
||||
To do this, in your `setup.py` add:
|
||||
|
||||
```python
|
||||
setup(
|
||||
...
|
||||
entry_points={
|
||||
'jupyterhub.authenticators': [
|
||||
'myservice = mypackage:MyAuthenticator',
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
If you have added this metadata to your package,
|
||||
users can select your authenticator with the configuration:
|
||||
|
||||
```python
|
||||
c.JupyterHub.authenticator_class = 'myservice'
|
||||
```
|
||||
|
||||
instead of the full
|
||||
|
||||
```python
|
||||
c.JupyterHub.authenticator_class = 'mypackage:MyAuthenticator'
|
||||
```
|
||||
|
||||
previously required.
|
||||
Additionally, configurable attributes for your spawner will
|
||||
appear in jupyterhub help output and auto-generated configuration files
|
||||
via `jupyterhub --generate-config`.
|
||||
|
||||
|
||||
### Authentication state
|
||||
|
||||
|
@@ -10,6 +10,7 @@ and a custom Spawner needs to be able to take three actions:
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
Custom Spawners for JupyterHub can be found on the [JupyterHub wiki](https://github.com/jupyterhub/jupyterhub/wiki/Spawners).
|
||||
Some examples include:
|
||||
|
||||
@@ -174,6 +175,42 @@ When `Spawner.start` is called, this dictionary is accessible as `self.user_opti
|
||||
|
||||
If you are interested in building a custom spawner, you can read [this tutorial](http://jupyterhub-tutorial.readthedocs.io/en/latest/spawners.html).
|
||||
|
||||
### Registering custom Spawners via entry points
|
||||
|
||||
As of JupyterHub 1.0, custom Spawners can register themselves via
|
||||
the `jupyterhub.spawners` entry point metadata.
|
||||
To do this, in your `setup.py` add:
|
||||
|
||||
```python
|
||||
setup(
|
||||
...
|
||||
entry_points={
|
||||
'jupyterhub.spawners': [
|
||||
'myservice = mypackage:MySpawner',
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
If you have added this metadata to your package,
|
||||
users can select your authenticator with the configuration:
|
||||
|
||||
```python
|
||||
c.JupyterHub.spawner_class = 'myservice'
|
||||
```
|
||||
|
||||
instead of the full
|
||||
|
||||
```python
|
||||
c.JupyterHub.spawner_class = 'mypackage:MySpawner'
|
||||
```
|
||||
|
||||
previously required.
|
||||
Additionally, configurable attributes for your spawner will
|
||||
appear in jupyterhub help output and auto-generated configuration files
|
||||
via `jupyterhub --generate-config`.
|
||||
|
||||
|
||||
## Spawners, resource limits, and guarantees (Optional)
|
||||
|
||||
Some spawners of the single-user notebook servers allow setting limits or
|
||||
|
@@ -42,7 +42,7 @@ from traitlets import (
|
||||
Tuple, Type, Set, Instance, Bytes, Float,
|
||||
observe, default,
|
||||
)
|
||||
from traitlets.config import Application, catch_config_error
|
||||
from traitlets.config import Application, Configurable, catch_config_error
|
||||
|
||||
here = os.path.dirname(__file__)
|
||||
|
||||
@@ -58,7 +58,7 @@ from .oauth.provider import make_provider
|
||||
from ._data import DATA_FILES_PATH
|
||||
from .log import CoroutineLogFormatter, log_request
|
||||
from .proxy import Proxy, ConfigurableHTTPProxy
|
||||
from .traitlets import URLPrefix, Command
|
||||
from .traitlets import URLPrefix, Command, EntryPointType
|
||||
from .utils import (
|
||||
maybe_future,
|
||||
url_path_join,
|
||||
@@ -227,13 +227,19 @@ class JupyterHub(Application):
|
||||
'upgrade-db': (UpgradeDB, "Upgrade your JupyterHub state database to the current version."),
|
||||
}
|
||||
|
||||
classes = List([
|
||||
Spawner,
|
||||
LocalProcessSpawner,
|
||||
Authenticator,
|
||||
PAMAuthenticator,
|
||||
CryptKeeper,
|
||||
])
|
||||
classes = List()
|
||||
@default('classes')
|
||||
def _load_classes(self):
|
||||
classes = [Spawner, Authenticator, CryptKeeper]
|
||||
for name, trait in self.traits(config=True).items():
|
||||
# load entry point groups into configurable class list
|
||||
# so that they show up in config files, etc.
|
||||
if isinstance(trait, EntryPointType):
|
||||
for key, entry_point in trait.load_entry_points().items():
|
||||
cls = entry_point.load()
|
||||
if cls not in classes and isinstance(cls, Configurable):
|
||||
classes.append(cls)
|
||||
return classes
|
||||
|
||||
load_groups = Dict(List(Unicode()),
|
||||
help="""Dict of 'group': ['usernames'] to load at startup.
|
||||
@@ -651,20 +657,25 @@ class JupyterHub(Application):
|
||||
).tag(config=True)
|
||||
_service_map = Dict()
|
||||
|
||||
authenticator_class = Type(PAMAuthenticator, Authenticator,
|
||||
authenticator_class = EntryPointType(
|
||||
default_value=PAMAuthenticator,
|
||||
klass=Authenticator,
|
||||
entry_point_group="jupyterhub.authenticators",
|
||||
help="""Class for authenticating users.
|
||||
|
||||
This should be a class with the following form:
|
||||
This should be a subclass of :class:`jupyterhub.auth.Authenticator`
|
||||
|
||||
- constructor takes one kwarg: `config`, the IPython config object.
|
||||
|
||||
with an authenticate method that:
|
||||
with an :meth:`authenticate` method that:
|
||||
|
||||
- is a coroutine (asyncio or tornado)
|
||||
- returns username on success, None on failure
|
||||
- takes two arguments: (handler, data),
|
||||
where `handler` is the calling web.RequestHandler,
|
||||
and `data` is the POST form data from the login page.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
authenticators may be registered via entry points,
|
||||
e.g. `c.JupyterHub.authenticator_class = 'pam'`
|
||||
"""
|
||||
).tag(config=True)
|
||||
|
||||
@@ -679,10 +690,17 @@ class JupyterHub(Application):
|
||||
).tag(config=True)
|
||||
|
||||
# class for spawning single-user servers
|
||||
spawner_class = Type(LocalProcessSpawner, Spawner,
|
||||
spawner_class = EntryPointType(
|
||||
default_value=LocalProcessSpawner,
|
||||
klass=Spawner,
|
||||
entry_point_group="jupyterhub.spawners",
|
||||
help="""The class to use for spawning single-user servers.
|
||||
|
||||
Should be a subclass of Spawner.
|
||||
Should be a subclass of :class:`jupyterhub.spawner.Spawner`.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
spawners may be registered via entry points,
|
||||
e.g. `c.JupyterHub.spawner_class = 'localprocess'`
|
||||
"""
|
||||
).tag(config=True)
|
||||
|
||||
|
@@ -4,7 +4,8 @@ Traitlets that are used in JupyterHub
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from traitlets import List, Unicode, Integer, TraitType, TraitError
|
||||
import entrypoints
|
||||
from traitlets import List, Unicode, Integer, Type, TraitType, TraitError
|
||||
|
||||
|
||||
class URLPrefix(Unicode):
|
||||
@@ -91,3 +92,46 @@ class Callable(TraitType):
|
||||
return value
|
||||
else:
|
||||
self.error(obj, value)
|
||||
|
||||
|
||||
class EntryPointType(Type):
|
||||
"""Entry point-extended Type
|
||||
|
||||
classes can be registered via entry points
|
||||
in addition to standard 'mypackage.MyClass' strings
|
||||
"""
|
||||
|
||||
_original_help = ''
|
||||
|
||||
def __init__(self, *args, entry_point_group, **kwargs):
|
||||
self.entry_point_group = entry_point_group
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def help(self):
|
||||
"""Extend help by listing currently installed choices"""
|
||||
chunks = [self._original_help]
|
||||
chunks.append("Currently installed: ")
|
||||
for key, entry_point in self.load_entry_points().items():
|
||||
chunks.append(" - {}: {}.{}".format(key, entry_point.module_name, entry_point.object_name))
|
||||
return '\n'.join(chunks)
|
||||
|
||||
@help.setter
|
||||
def help(self, value):
|
||||
self._original_help = value
|
||||
|
||||
def load_entry_points(self):
|
||||
"""Load my entry point group"""
|
||||
# load the group
|
||||
group = entrypoints.get_group_named(self.entry_point_group)
|
||||
# make it case-insensitive
|
||||
return {key.lower(): value for key, value in group.items()}
|
||||
|
||||
def validate(self, obj, value):
|
||||
if isinstance(value, str):
|
||||
# first, look up in entry point registry
|
||||
registry = self.load_entry_points()
|
||||
key = value.lower()
|
||||
if key in registry:
|
||||
value = registry[key].load()
|
||||
return super().validate(obj, value)
|
||||
|
@@ -1,5 +1,6 @@
|
||||
alembic
|
||||
async_generator>=1.8
|
||||
entrypoints
|
||||
traitlets>=4.3.2
|
||||
tornado>=5.0
|
||||
jinja2
|
||||
|
11
setup.py
11
setup.py
@@ -106,6 +106,17 @@ setup_args = dict(
|
||||
platforms = "Linux, Mac OS X",
|
||||
keywords = ['Interactive', 'Interpreter', 'Shell', 'Web'],
|
||||
python_requires = ">=3.5",
|
||||
entry_points = {
|
||||
'jupyterhub.authenticators': [
|
||||
'default = jupyterhub.auth:PAMAuthenticator',
|
||||
'pam = jupyterhub.auth:PAMAuthenticator',
|
||||
'dummy = jupyterhub.auth:DummyAuthenticator',
|
||||
],
|
||||
'jupyterhub.spawners': [
|
||||
'default = jupyterhub.spawner:LocalProcessSpawner',
|
||||
'localprocess = jupyterhub.spawner:LocalProcessSpawner',
|
||||
],
|
||||
},
|
||||
classifiers = [
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: System Administrators',
|
||||
|
Reference in New Issue
Block a user