Skip to content

Commit 7520d32

Browse files
committed
rename legacy backend, cleanup tests and comments
1 parent f85372f commit 7520d32

File tree

13 files changed

+241
-709
lines changed

13 files changed

+241
-709
lines changed

conftest.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,33 @@
11
from pathlib import Path
2+
from unittest.mock import patch
23

34
import pytest
45
from sqlalchemy import create_engine
56
from sqlalchemy.orm import sessionmaker
67

78
from jupyter_scheduler.orm import Base
89
from jupyter_scheduler.scheduler import Scheduler
9-
from jupyter_scheduler.tests.mocks import MockEnvironmentManager
10+
from jupyter_scheduler.tests.mocks import MockEnvironmentManager, MockTestBackend
1011

1112
pytest_plugins = ("jupyter_server.pytest_plugin", "pytest_jupyter.jupyter_server")
1213

1314

15+
def _mock_discover_backends(*args, **kwargs):
16+
"""Return test backends for testing - includes high-priority test backend."""
17+
from jupyter_scheduler.backends import JupyterServerNotebookBackend
18+
19+
return {"jupyter_server_nb": JupyterServerNotebookBackend, "test": MockTestBackend}
20+
21+
22+
@pytest.fixture(autouse=True)
23+
def mock_backend_discovery():
24+
"""Patch backend discovery to include test backend for all tests."""
25+
with patch(
26+
"jupyter_scheduler.extension.discover_backends", side_effect=_mock_discover_backends
27+
):
28+
yield
29+
30+
1431
@pytest.fixture(scope="session")
1532
def static_test_files_dir() -> Path:
1633
return Path(__file__).parent.resolve() / "jupyter_scheduler" / "tests" / "static"
@@ -51,6 +68,7 @@ def jp_scheduler_db(jp_scheduler_db_url):
5168
session = Session()
5269
yield session
5370
session.close()
71+
engine.dispose()
5472

5573

5674
@pytest.fixture
Lines changed: 10 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
"""Backend registry for managing multiple backend configurations.
2-
3-
This module provides a registry for managing multiple scheduler backends.
4-
Each backend is a complete execution environment with its own scheduler,
5-
execution manager, and optionally database manager.
6-
"""
7-
81
import logging
92
from dataclasses import dataclass
103
from typing import Any, Dict, List, Optional, Type
@@ -18,54 +11,22 @@
1811

1912

2013
def import_class(class_path: str) -> Type:
21-
"""Import a class from a fully qualified class path.
22-
23-
Parameters
24-
----------
25-
class_path : str
26-
Fully qualified class path (e.g., "jupyter_scheduler.scheduler.Scheduler")
27-
28-
Returns
29-
-------
30-
Type
31-
The imported class
32-
"""
14+
"""Import a class from a fully qualified path like 'module.submodule.ClassName'."""
3315
module_path, class_name = class_path.rsplit(".", 1)
3416
module = __import__(module_path, fromlist=[class_name])
3517
return getattr(module, class_name)
3618

3719

3820
@dataclass
3921
class BackendInstance:
40-
"""A running instance of a backend with initialized scheduler.
41-
42-
Attributes
43-
----------
44-
config : BackendConfig
45-
The configuration used to create this backend
46-
scheduler : BaseScheduler
47-
The initialized scheduler instance for this backend
48-
"""
22+
"""A running backend with its configuration and initialized scheduler."""
4923

5024
config: BackendConfig
5125
scheduler: BaseScheduler
5226

5327

5428
class BackendRegistry:
55-
"""Registry managing multiple backend configurations.
56-
57-
This class is responsible for:
58-
- Storing and managing multiple backend configurations
59-
- Creating and initializing backend instances (schedulers)
60-
- Routing requests to the appropriate backend based on ID or file extension
61-
62-
Parameters
63-
----------
64-
configs : List[BackendConfig]
65-
List of backend configurations to register
66-
default_backend : str
67-
The ID of the default backend to use when none is specified
68-
"""
29+
"""Registry for storing, initializing, and routing to scheduler backends."""
6930

7031
def __init__(self, configs: List[BackendConfig], default_backend: str):
7132
self._configs = configs
@@ -80,19 +41,7 @@ def initialize(
8041
db_url: str,
8142
config: Optional[Any] = None,
8243
):
83-
"""Instantiate all backends from configs.
84-
85-
Parameters
86-
----------
87-
root_dir : str
88-
The Jupyter server root directory
89-
environments_manager : EnvironmentManager
90-
The environment manager instance to use
91-
db_url : str
92-
Default database URL (used if backend doesn't specify its own)
93-
config : Any, optional
94-
Traitlets config object
95-
"""
44+
"""Instantiate all backends from configs."""
9645
for cfg in self._configs:
9746
try:
9847
instance = self._create_backend(cfg, root_dir, environments_manager, db_url, config)
@@ -118,26 +67,7 @@ def _create_backend(
11867
global_db_url: str,
11968
config: Optional[Any] = None,
12069
) -> BackendInstance:
121-
"""Create a backend instance from configuration.
122-
123-
Parameters
124-
----------
125-
cfg : BackendConfig
126-
The backend configuration
127-
root_dir : str
128-
The Jupyter server root directory
129-
environments_manager : EnvironmentManager
130-
The environment manager instance
131-
global_db_url : str
132-
Default database URL (used if backend doesn't specify its own)
133-
config : Any, optional
134-
Traitlets config object
135-
136-
Returns
137-
-------
138-
BackendInstance
139-
The initialized backend instance
140-
"""
70+
"""Create a backend instance from configuration."""
14171
scheduler_class = import_class(cfg.scheduler_class)
14272

14373
# Use backend-specific db_url if provided, otherwise use global
@@ -163,75 +93,30 @@ def _create_backend(
16393
return BackendInstance(config=cfg, scheduler=scheduler)
16494

16595
def get_backend(self, backend_id: str) -> Optional[BackendInstance]:
166-
"""Get a backend by its ID.
167-
168-
Parameters
169-
----------
170-
backend_id : str
171-
The backend ID to look up
172-
173-
Returns
174-
-------
175-
BackendInstance or None
176-
The backend instance if found, None otherwise
177-
"""
96+
"""Get a backend by ID, or None if not found."""
17897
return self._backends.get(backend_id)
17998

18099
def get_default(self) -> BackendInstance:
181-
"""Get the default backend.
182-
183-
Returns
184-
-------
185-
BackendInstance
186-
The default backend instance
187-
188-
Raises
189-
------
190-
KeyError
191-
If the default backend is not found
192-
"""
100+
"""Get the default backend."""
193101
if self._default not in self._backends:
194102
raise KeyError(f"Default backend '{self._default}' not found in registry")
195103
return self._backends[self._default]
196104

197105
def get_for_file(self, input_uri: str) -> BackendInstance:
198-
"""Auto-select backend based on file extension.
199-
200-
If multiple backends support the file type, returns the one with
201-
highest priority. If no backend matches the extension, returns
202-
the default backend.
203-
204-
Parameters
205-
----------
206-
input_uri : str
207-
The input file URI/path
208-
209-
Returns
210-
-------
211-
BackendInstance
212-
The selected backend instance
213-
"""
214-
# Extract file extension
106+
"""Auto-select backend by file extension (highest priority wins), or return default."""
215107
ext = ""
216108
if "." in input_uri:
217109
ext = input_uri.rsplit(".", 1)[-1].lower()
218110

219111
candidates = self._extension_map.get(ext, [])
220112
if candidates:
221-
# Return highest priority backend
222113
candidate_instances = [self._backends[bid] for bid in candidates]
223114
return max(candidate_instances, key=lambda b: b.config.priority)
224115

225116
return self.get_default()
226117

227118
def list_backends(self) -> List[DescribeBackend]:
228-
"""Return list of backends for API/UI.
229-
230-
Returns
231-
-------
232-
List[DescribeBackend]
233-
List of backend descriptions for frontend consumption
234-
"""
119+
"""Return backend descriptions for API/UI consumption."""
235120
return [
236121
DescribeBackend(
237122
id=b.config.id,
@@ -244,19 +129,11 @@ def list_backends(self) -> List[DescribeBackend]:
244129
]
245130

246131
def list_backend_instances(self) -> List[BackendInstance]:
247-
"""Return list of all backend instances.
248-
249-
Returns
250-
-------
251-
List[BackendInstance]
252-
List of all backend instances
253-
"""
132+
"""Return all backend instances."""
254133
return list(self._backends.values())
255134

256135
def __len__(self) -> int:
257-
"""Return the number of registered backends."""
258136
return len(self._backends)
259137

260138
def __contains__(self, backend_id: str) -> bool:
261-
"""Check if a backend ID is registered."""
262139
return backend_id in self._backends

jupyter_scheduler/backend_utils.py

Lines changed: 6 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
"""Backend discovery utilities.
2-
3-
This module provides functions for discovering scheduler backends registered
4-
via Python entry points. The entry point group "jupyter_scheduler.backends"
5-
is scanned at startup to find all available backend implementations.
6-
7-
The discovery system supports:
8-
- Automatic registration of pip-installed backend packages
9-
- Allow/block lists for filtering available backends
10-
- Graceful handling of missing dependencies
11-
"""
12-
131
import logging
142
from importlib.metadata import entry_points
153
from typing import Dict, List, Optional, Type
@@ -26,53 +14,16 @@ def discover_backends(
2614
allowed_backends: Optional[List[str]] = None,
2715
blocked_backends: Optional[List[str]] = None,
2816
) -> Dict[str, Type[BaseBackend]]:
29-
"""Discover all registered backends via entry points.
30-
31-
Scans the "jupyter_scheduler.backends" entry point group for registered
32-
backend classes. Each entry point should reference a class that inherits
33-
from BaseBackend.
34-
35-
Parameters
36-
----------
37-
log : logging.Logger, optional
38-
Logger for status messages. If None, uses module logger.
39-
allowed_backends : list of str, optional
40-
If provided, only backends with IDs in this list are included.
41-
Takes precedence over blocked_backends for the same ID.
42-
blocked_backends : list of str, optional
43-
If provided, backends with IDs in this list are excluded.
44-
45-
Returns
46-
-------
47-
dict
48-
Mapping of backend_id -> backend class for all discovered backends.
49-
50-
Notes
51-
-----
52-
Backends are filtered in this order:
53-
1. Entry point is loaded (skip on ImportError with warning)
54-
2. Backend ID is checked against blocked_backends (skip if blocked)
55-
3. Backend ID is checked against allowed_backends (skip if not allowed)
56-
57-
Example entry point registration in pyproject.toml:
58-
59-
[project.entry-points."jupyter_scheduler.backends"]
60-
local = "jupyter_scheduler.backends:LocalBackend"
61-
k8s = "jupyter_scheduler_k8s:K8sBackend"
62-
"""
17+
"""Discover backends registered in the 'jupyter_scheduler.backends' entry point group."""
6318
if log is None:
6419
log = logger
6520

6621
backends: Dict[str, Type[BaseBackend]] = {}
6722

68-
# Get entry points for the backends group
69-
# Compatible with Python 3.9+ importlib.metadata
7023
eps = entry_points()
7124
if hasattr(eps, "select"):
72-
# Python 3.10+ / importlib_metadata style
7325
backend_eps = eps.select(group=ENTRY_POINT_GROUP)
7426
else:
75-
# Python 3.9 style (returns dict)
7627
backend_eps = eps.get(ENTRY_POINT_GROUP, [])
7728

7829
for ep in backend_eps:
@@ -118,49 +69,20 @@ def get_default_backend_id(
11869
available_backends: Dict[str, Type[BaseBackend]],
11970
configured_default: Optional[str] = None,
12071
) -> str:
121-
"""Determine the default backend ID.
122-
123-
Selection priority:
124-
1. Explicitly configured default (if available)
125-
2. "local" backend (if available)
126-
3. First available backend (sorted by ID for determinism)
127-
128-
Parameters
129-
----------
130-
available_backends : dict
131-
Mapping of backend_id -> backend class from discover_backends().
132-
configured_default : str, optional
133-
Administrator-configured default backend ID.
134-
135-
Returns
136-
-------
137-
str
138-
The backend ID to use as default.
139-
140-
Raises
141-
------
142-
ValueError
143-
If no backends are available.
144-
"""
72+
"""Select default backend: configured > 'jupyter_server_nb' > first alphabetically."""
14573
if not available_backends:
146-
raise ValueError(
147-
"No scheduler backends available. " "Ensure at least one backend package is installed."
148-
)
74+
raise ValueError("No scheduler backends available.")
14975

150-
# Explicit configuration takes precedence
15176
if configured_default and configured_default in available_backends:
15277
return configured_default
15378

154-
# Warn if configured default is not available
15579
if configured_default and configured_default not in available_backends:
15680
logger.warning(
15781
f"Configured default_backend '{configured_default}' not found. "
158-
f"Available backends: {list(available_backends.keys())}"
82+
f"Available: {list(available_backends.keys())}"
15983
)
16084

161-
# Fall back to "local" if available
162-
if "local" in available_backends:
163-
return "local"
85+
if "jupyter_server_nb" in available_backends:
86+
return "jupyter_server_nb"
16487

165-
# Last resort: first available (sorted for determinism)
16688
return sorted(available_backends.keys())[0]

0 commit comments

Comments
 (0)