Lightbulb

repository·master·Indexed 42 days ago

https://github.com/tandemdude/hikari-lightbulb

An easy-to-use command handler library designed to integrate with Hikari, a Discord API wrapper for Python. Lightbulb provides a structured way to create and register SlashCommands, UserCommands, and MessageCommands, including support for command options and hierarchical command groups. As of version 3, prefix command support has been removed.

Tokens
24K
Snippets
68
Records
107
Agent score
52%

What's inside Lightbulb

  1. Use Ephemeral Dependencies for runtime registration

    master

    Because registries are frozen once a container is created, you cannot add new dependencies to a registry after the fact. However, you can add ephemeral dependencies directly to an active container.

    Ephemeral dependencies follow the lifecycle of the container and are destroyed when the container is closed. Use these for objects that are only known at runtime (like a specific command context).

    Methods to add ephemeral dependencies:

    • .Container.add_value(type, value)
    • .Container.add_factory(type, factory)
  2. Accessing DI Containers

    master

    When a container is active, it can be injected using the type hint linkd.containers.Container. This will return the most-recently activated container for the current context.

    Lightbulb provides specific container types for different contexts:

    • lightbulb.di.DefaultContainer: The default global container.
    • lightbulb.di.CommandContainer: The container for a command execution.
    • lightbulb.di.AutocompleteContainer: The container for an autocomplete flow.
    • lightbulb.di.ListenerContainer: The container for a listener.
    • lightbulb.di.TaskContainer: The container for a task.

    You can inject these containers to register ephemeral dependencies that only exist within that specific context.

    import linkd.containers
    
    @lightbulb.invoke
    async def invoke(self, ctx: lightbulb.Context, container: linkd.containers.Container):
        # container will be a lightbulb.di.CommandContainer in this context
        ...
  3. How the Dependency Injection context hierarchy works

    master

    Lightbulb's DI system uses a hierarchical context model. Dependencies registered in a base context are inherited by all derived contexts.

    • lightbulb.di.Contexts.DEFAULT: The base context. Dependencies registered here are 'globally' available during all Lightbulb-controlled flows.
    • lightbulb.di.Contexts.COMMAND: Available only during command execution (including hooks and error handlers).
    • lightbulb.di.Contexts.AUTOCOMPLETE: Available only during autocomplete handler execution.
    • lightbulb.di.Contexts.LISTENER: Available only during listener execution (specifically for listeners registered to Loaders).
    • lightbulb.di.Contexts.TASK: Available only during task execution.

    When a 'Flow' (like a command or task) starts, a flow-specific context is created, inheriting from the DEFAULT context. This allows you to define dependencies that are scoped specifically to the lifecycle of a single command or task.

  4. Register dependencies with dependencies (Factory Injection)

    master

    When registering a dependency via a factory, the factory method can require other registered dependencies. Lightbulb will resolve and provide those dependencies before calling your factory.

    Factory Requirements: To ensure successful resolution, every parameter in your factory method:

    • MUST NOT have a default value, unless the default is exactly lightbulb.di.INJECTED.
    • MUST NOT be positional-only, var positional (*args), or var keyword (**kwargs).
    • SHOULD have a type-hint. If no type-hint is provided, Lightbulb attempts to resolve the dependency by the parameter name (this only applies to factory methods; for injectable methods, type-hints are mandatory).
    import aiohttp
    import lightbulb
    
    class Config:
        base_url: str
    
    client = lightbulb.client_from_app(...)
    registry = client.di.registry_for(lightbulb.di.Contexts.DEFAULT)
    registry.register_value(Config, Config())
    
    # The factory 'create_client_session' depends on 'Config'
    def create_client_session(cfg: Config) -> aiohttp.ClientSession:
        return aiohttp.ClientSession(cfg.base_url)
    
    registry.register_factory(aiohttp.ClientSession, create_client_session)
  5. Implement error handlers for command execution

    master

    Error handlers allow your bot to respond to errors occurring during command execution (or within command hooks) instead of simply failing silently.

    Key Constraints:

    • Handlers only apply to errors raised during command execution. For errors occurring during listener invocation, you must create a listener for Hikari's ExceptionEvent instead.
    • An error handler must be an async function.
    • The first argument must be an instance of lightbulb.exceptions.ExecutionPipelineFailedException.
    • The function must return a boolean:
      • Return True if the error was handled (this stops the error from propagating to other handlers).
      • Return False if the error should be passed to the next handler in the hierarchy.
    async def example_error_handler(exc: lightbulb.exceptions.ExecutionPipelineFailedException) -> bool:
        # Return True if the error was handled, otherwise False.
        return True
  6. Declare commands in Lightbulb

    master

    Commands are defined by subclassing one of the provided command base classes. Each command is represented by a single class.

    Available command bases:

    • lightbulb.SlashCommand: For Discord slash commands.
    • lightbulb.MessageCommand: For message context menu commands.
    • lightbulb.UserCommand: For user context menu commands.

    When declaring a SlashCommand, you must provide a name and a description as named parameters in the class definition.

    class YourCommand(lightbulb.SlashCommand, name="test-command", description="a test slash command"):
        ...
  7. Use Union and Optional types for dependency fallbacks

    master

    Lightbulb supports Python type unions and typing.Optional to provide fallback mechanisms when a dependency is missing.

    • Unions (TypeA | TypeB): Lightbulb checks dependencies in the order specified. If TypeA is not registered, it attempts to provide TypeB.
    • Optional (TypeA | None or Optional[TypeA]): If TypeA is not registered, the parameter is set to None instead of raising an error.

    Advanced Resolution with Try[]: Use lightbulb.di.Try to change the behavior so that if a dependency is registered but its creation fails, Lightbulb will fall back to the next type in the union sequence.

    Note: Using TypeA without a meta-annotation is functionally equivalent to If[TypeA].

    from lightbulb.di import Try
    
    # If Bar fails to initialize, it falls back to Baz
    async def dependency_factory(foo: Try[Bar] | Baz) -> Bork:
        ...
    
    # If Bar is not registered at all, foo is None
    async def optional_factory(foo: Bar | None) -> Baz:
        ...
  8. Override dependencies in child containers

    master

    Dependency overriding occurs when a child container (e.g., a COMMAND context) defines a dependency of the same type as one in its parent container (e.g., DEFAULT).

    Resolution Rules:

    1. When resolving a dependency, the container first looks in its own scope.
    2. It can look into its child containers if needed.
    3. It never looks into its parent containers for dependencies (it only looks up the hierarchy if the current container doesn't have it, but a child's override prevents the parent's version from being seen by the child).
    4. Scope of Override: An overridden dependency is only accessible to the child container and any of its own children. Parent containers cannot access the overridden value.