Service locator

Table of contents

Introduction

Features register replaceable service classes, and routes look up the current implementation at request time, after every feature, refiners included, has loaded. That is what lets a refiner swap a service without the base knowing.

# splent_feature_notes/__init__.py
from splent_framework.services.service_locator import register_service
from .services import NotesService

def init_feature(app):
    register_service(app, "NotesService", NotesService)

Using a service in your routes

# splent_feature_notes/routes.py
from splent_framework.services.service_locator import service_proxy

notes_service = service_proxy("NotesService")

@notes_bp.route("/notes/", methods=["GET"])
@login_required
def index():
    notes = notes_service.get_by_user(current_user.id)
    ...

service_proxy resolves the registered class at request time, the way Flask resolves current_app.

Never import the service class in routes (from .services import NotesService). The import creates the instance when the module loads, before refiners register their overrides.


Overriding a service from a refiner

# splent_feature_notes_tags/__init__.py
from splent_framework.refinement import refine_service
from .services import NotesServiceWithTags

def init_feature(app):
    refine_service(app, "NotesService", NotesServiceWithTags)
[tool.splent.refinement.overrides]
services = [{ target = "NotesService", replacement = "NotesServiceWithTags" }]

refine_service builds a class that inherits from both, so super() works, and service_proxy("NotesService") returns it. An undeclared call stops startup with FeatureError, and so does a second refiner of the same service. A call from outside any feature, such as a test, is not checked.

Replacement class Result
A plain class without __init__ Merged with the base. Its methods win, and super() reaches the base
A subclass of the base service Registered as it is
A class with its own __init__ that does not subclass the base Refused at startup, since its super().__init__(...) would reach the base’s. Drop the __init__, or subclass the base

A refiner always uses refine_service. register_service under another feature’s name bypasses every check and silently wins.


See also


Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es