Services

Table of contents

  1. Introduction
  2. Using the methods you get
  3. Putting a rule in a service
  4. Answering a form from a route
  5. Registering a service
    1. Replacing another feature’s service
  6. See also

Introduction

A service holds your feature’s business rules. It takes data, applies the rules through a repository and returns results, never an HTTP response. Routes, commands, signal handlers and tests all call the same service, so the rules live in one place.

from splent_framework.services.BaseService import BaseService
from splent_io.splent_feature_notes.repositories import NotesRepository


class NotesService(BaseService):
    def __init__(self):
        super().__init__(NotesRepository())

    def get_by_user(self, user_id):
        return self.repository.get_by_column("user_id", user_id)

Using the methods you get

BaseService hands these to its repository.

Method What it does
create(**kwargs) Creates a record and commits
get_by_id(id) The record, or None
get_or_404(id) The record, or a 404
update(id, **kwargs) Sets the fields and commits. None when the id does not exist
delete(id) Deletes and commits. False when the id does not exist
count() How many records there are

Anything else your service needs goes through self.repository, which also gives you get_by_column and delete_by_column. See Repositories.


Putting a rule in a service

from flask import current_app


class NotesService(BaseService):
    def create_note(self, *, title, content):
        title = (title or "").strip()
        if not title:
            raise ValueError("Title is required.")
        try:
            note = self.create(title=title, content=content)
        except Exception:
            self.repository.session.rollback()
            raise
        note_created.send(current_app._get_current_object(), note=note)
        return note

The rule holds whoever calls it. On a broken rule the service raises ValueError with a message, and the route decides whether to flash it, return JSON or log it.

A service never returns redirect(...) or a template. A signal handler or a command has no request to answer, so data is the only answer every caller can use.


Answering a form from a route

from splent_framework.utils.form_helpers import form_error, form_success


@notes_bp.route("/notes/<int:note_id>/edit", methods=["GET", "POST"])
@login_required
def edit(note_id):
    note = notes_service.get_or_404(note_id)
    form = NoteForm(obj=note)
    if request.method == "POST":
        if form.validate():
            notes_service.update(note.id, title=form.title.data)
            return form_success("notes.index", "Note updated.")
        return form_error("notes/edit.html", form, form.errors, note=note)
    return render_template("notes/edit.html", form=form, note=note)

These helpers are HTTP, so they live in the route, never in the service.

Helper What it does
form_success(endpoint, message, category="success", **url_kwargs) Flashes message and redirects to url_for(endpoint, **url_kwargs)
form_error(template, form, errors=None, **context) Flashes each field error as "<field>: <message>" under error, and renders template again with form
form_int(form, key, default=0, low=None, high=None) The whole number posted as key, or default when it is missing or not a number, kept between low and high

Registering a service

Register your services in init_feature, so other features find them, and refine them, by name.

from splent_framework.services.service_locator import register_service


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

Routes reach them through service_proxy, which builds the registered class on each use.

from splent_framework.services.service_locator import service_proxy

notes_service = service_proxy("NotesService")

Replacing another feature’s service

from splent_framework.refinement import refine_service


def init_feature(app):
    refine_service(app, "NotesService", NotesServiceWithTags)

A refinement feature declares the service in [tool.splent.refinement.overrides] services first. An undeclared call, or a second feature refining the same service, stops the start. See Service locator.

register_service under another feature’s name replaces its service with no check. Use refine_service.


See also


Back to top

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