Skip to main content

A Lightweight Service Locator for Python

Project description

svcs

svcs: A Lightweight Service Locator for Python

Warning ☠️ Not ready yet! ☠️

This project is only public to gather feedback, and everything can and will change until the project is proclaimed stable.

Currently only Flask support is production-ready, but API details can still change.

At this point, it's unclear whether this project will become a "proper Hynek project". I will keep using it for my work projects, but whether this will grow beyond my personal needs depends on community interest.

svcs (pronounced services) is a service locator for Python. It provides you with a central place to register factories for types/interfaces and then imperatively request instances of those types with automatic cleanup and health checks.


This allows you to configure and manage all your resources in one central place and access them in a consistent way.


In practice that means that at runtime, you say "Give me a database connection!", and svcs will give you whatever you've configured it to return when asked for a database connection. This can be an actual database connection or it can be a mock object for testing. All of this happens within your application – service locators are not related to service discovery.

If you like the Dependency Inversion Principle (aka "program against interfaces, not implementations"), you would register concrete factories for abstract interfaces; in Python usually a Protocol or an Abstract Base Class.

That:

  • unifies acquisition and cleanups of resources,
  • simplifies testing,
  • and allows for easy health checks across all resources.

No global mutable state is necessary – but possible for extra comfort.

The goal is to minimize your business code to:

def view(request):
    db = request.services.get(Database)
    api = request.services.get(WebAPIClient)

or even:

def view():
    db = services.get(Database)
    api = services.get(WebAPIClient)

The latter already works with Flask.

You set it up like this:

import atexit

from sqlalchemy import Connection, create_engine

...

engine = create_engine("postgresql://localhost")

def engine_factory():
    with engine.connect() as conn:
        yield conn

registry = svcs.Registry()
registry.register_factory(
    Connection, engine_factory, on_registry_close=engine.dispose
)

@atexit.register
def cleanup():
    registry.close()  # calls engine.dispose()

The generator-based setup and cleanup may remind you of Pytest fixtures. The hooks that are defined as on_registry_close are called when you call Registry.close() – e.g. when your application is shutting down.

svcs comes with full async support via a-prefixed methods (i.e. aget() instead of get(), et cetera).

Is this Dependency Injection!?

No.

Unlike dependency injection, which passes your dependencies as arguments, you actively ask a service locator for them. This usually requires less opaque magic since nothing meddles with your function/method definitions. But you can use, e.g., your web framework's injection capabilities to inject the locator object into your views and benefit from svcs's upsides without giving up some of DI's ones.

The active acquisition of resources by calling get() when you know for sure you're going to need it avoids the conundrum of either having to pass a factory (e.g., a connection pool – which also puts the onus of cleanup on you) or eagerly creating resources that you never use:

def view(request):
    if request.form.valid():
        # Form is valid; only NOW get a DB connection
        # and pass it into your business logic.
        return handle_form_data(
            request.services.get(Database),
            form.data,
        )

    raise InvalidFormError()

The main downside is that it's impossible to verify whether all required dependencies have been configured without running the code.


For now, please refer to the GitHub README for latest documentation.

Release Information

Added

  • Registered factory/value clean up! It is now possible to register an on_registry_close hook that is called once the Registry's (a)close() method is called.

→ Full Changelog

Credits

svcs is written by Hynek Schlawack and distributed under the terms of the MIT license.

The development is kindly supported by my employer Variomedia AG and all my amazing GitHub Sponsors.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

svcs-23.5.0.tar.gz (25.5 kB view details)

Uploaded Source

Built Distribution

svcs-23.5.0-py3-none-any.whl (8.7 kB view details)

Uploaded Python 3

File details

Details for the file svcs-23.5.0.tar.gz.

File metadata

  • Download URL: svcs-23.5.0.tar.gz
  • Upload date:
  • Size: 25.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/4.0.2 CPython/3.11.4

File hashes

Hashes for svcs-23.5.0.tar.gz
Algorithm Hash digest
SHA256 1a37ba47abb49407770fb9798a27e6ea07913c80f4a786c76a28003a36c18bdd
MD5 d89b197bf64b91119be583be4a6ab4c0
BLAKE2b-256 3e801d93b4edd84644bd03d0ce26ff38bb8b8ffbd8ce742f9fe9a8ceedbe3a91

See more details on using hashes here.

File details

Details for the file svcs-23.5.0-py3-none-any.whl.

File metadata

  • Download URL: svcs-23.5.0-py3-none-any.whl
  • Upload date:
  • Size: 8.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/4.0.2 CPython/3.11.4

File hashes

Hashes for svcs-23.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7e16e27652c317e13466b6b28215fda8707810e167ed709a5c28d96a821eb193
MD5 229db7ef42eeae38ed172351b79d5cd3
BLAKE2b-256 be6078d2840788f920cf1e97183cc70696a191aa43433e70bbbad395a4c4b081

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page