Skip to main content

A composable dependency container with passive UI and service facets.

Project description

facetkit

A composable Python container for application state and passive registries. Provides basic functionality for CLI commands, TUI screens, GUI widgets, web routes and background services.

Design

  • Container — shared config, component lifecycle, and a map of mounted facets
  • Facets — framework-agnostic registries (commands, routes, widgets, tasks, etc)
  • Components — plugins that register into facets on attach and clean up on detach
Container
├── config
├── components
└── facets
    ├── cli      → commands
    ├── tui      → screens, keybindings
    ├── gui      → widgets, menus, toolbars, layouts
    ├── web      → routes, middleware, error handlers
    └── service  → tasks, providers

Requirements

  • Python 3.10+
  • glom (declared as a dependency)

Installation

pip install facetkit

For local development:

git clone https://github.com/Dev-DanielR/py_facetkit.git
cd facetkit
pip install -e ".[dev]"

Quick start

from facetkit import Container, CliFacet, WebFacet

app = Container({"app": {"name": "demo"}})

app.mount_facet("cli", CliFacet())
app.mount_facet("web", WebFacet())

def hello():
    """Say hello."""
    return "Hello!"

cli = app.facets["cli"]
cli.add_command("hello", hello)

web = app.facets["web"]
web.add_route("hello", "/hello", lambda: {"message": "Hello!"}, methods=["GET"])

Your application reads the registries and dispatches however you like — argparse, FastAPI, Textual, Qt, etc.

Components

Components implement attach(ctx) and detach(ctx). The container is passed as context:

class StatusComponent:
    def attach(self, ctx):
        ctx.facets["cli"].add_command("status", self.show_status)
        ctx.facets["service"].add_provider("status", {"healthy": True})

    def detach(self, ctx):
        ctx.facets["cli"].remove_command("status")
        ctx.facets["service"].remove_provider("status")

    def show_status(self):
        """Show application status."""
        return "ok"

app.mount_facet("cli", CliFacet())
app.mount_facet("service", ServiceFacet())
app.add_component("status", StatusComponent())

Replacing or removing a component calls detach on the old instance before the new one attaches.

Facets

Facet Registries Purpose
CliFacet commands Named CLI commands. Description is taken from the handler's docstring
TuiFacet screens, keybindings, current_screen Terminal UI descriptors
GuiFacet widgets, menus, toolbars, layouts Desktop UI descriptors
WebFacet routes, middleware, error_handlers HTTP/API descriptors
ServiceFacet tasks, providers Background work and shared providers

Mount only what you need:

app.mount_facet("cli", CliFacet())
app.mount_facet("web", WebFacet())
# No TUI/GUI facets required

Unmounting clears the facet's registries:

app.unmount_facet("cli")

Registry behavior

  • Duplicate keys — registering the same name again overwrites the previous entry (last wins).
  • Unmountunmount_facet clears that facet's registries. Attached components are not automatically detached; call remove_component first if you need a full teardown.
  • Facet names — the mount name is arbitrary (app.mount_facet("cli", CliFacet())). The library does not enforce that the name matches the facet type.
  • Missing facetsapp.facets["cli"] raises KeyError; app.get("facets.cli") returns None.

Introspection with get()

Container.get(path) uses glom to read nested state:

app.get("")                        # the container itself
app.get("config.app.name")         # config values
app.get("facets")                  # all mounted facets
app.get("facets.cli")              # CliFacet instance
app.get("facets.cli.commands")     # command registry
app.get("facets.web.routes.users") # a single route entry

Missing paths return default (or None):

app.get("facets.missing")              # None
app.get("facets.missing", default={})  # {}

get() also returns default on any internal error while resolving a path, not only missing keys. That keeps introspection safe but can hide bugs — prefer direct attribute access when you want exceptions to surface.

Direct dict access also works when you know a facet is mounted:

app.facets["cli"].commands["hello"]

Public API

from facetkit import (
    Container,
    Component,     # protocol
    Facet,         # protocol
    CliFacet,
    TuiFacet,
    GuiFacet,
    WebFacet,
    ServiceFacet,
    Command,
    RouteDescriptor,
    # ... other descriptor types
)

Versioning

This project is pre-1.0 (0.1.0). Public APIs may change between minor releases. See CHANGELOG.md for release notes.

Development

pip install -e ".[dev]"
pytest

Build a wheel locally:

pip install build
python -m build

License

MIT — see LICENSE.

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

facetkit-0.1.0.tar.gz (9.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

facetkit-0.1.0-py3-none-any.whl (9.4 kB view details)

Uploaded Python 3

File details

Details for the file facetkit-0.1.0.tar.gz.

File metadata

  • Download URL: facetkit-0.1.0.tar.gz
  • Upload date:
  • Size: 9.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.1

File hashes

Hashes for facetkit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 fcf43aae22c23b2f6c3fc064b08acf3f6a3f8f088bc8bb6cd8398d52f1e60f9b
MD5 b40bbc7ab86aaf9fcebebefdfc34372c
BLAKE2b-256 a30eba0030616a8c7d2ffbc526159a5f9a3494393d6d18a00b195871019ab5dc

See more details on using hashes here.

File details

Details for the file facetkit-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: facetkit-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 9.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.1

File hashes

Hashes for facetkit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8ddc092be2fe740e0d2023af9aaab6a8d325840a48b99c609894261ba70a73a7
MD5 6ffa6dd94f72f6340f243e088fad7fef
BLAKE2b-256 5798d554553d99f486620cd2fb252a348c30d61453364653387f37f02273fca7

See more details on using hashes here.

Supported by

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