Skip to main content

Tiny framework for bootstrapping apps

Project description

bean.core

bean.core is a tiny Python toolkit to bootstrap small apps.

It gives you ready-to-use building blocks so you can focus on the business logic, instead of boilerplate.


Overview

With bean.core you get:

  • App lifecycle: start, run, shutdown
  • Config: load from multiple sources (env, .py, ...) with validation.
  • Logging.
  • Pipes: composable data flows.
  • Shell commands: run commands directly in your flows.
  • Scheduler: jobs & tasks.

Just enough to cook some beans.

Installation

Requirements:

  • Python 3.14+

Using pip:

pip install --upgrade bean-core

Using curl (direct download):

FILE="src/bean/core.py"
mkdir -p "$(dirname "$FILE")"
curl -Lso "$FILE" \
    https://raw.githubusercontent.com/numen-0/bean/refs/heads/main/bean-core/src/bean/core.py

Quick Examples

This is a quick reference for the main API.

For full details, peek at the source code :).

Simple Loop App

In this example:

  • define a custom app by overriding the run method and optionally startup and shutdown.
  • set up logging.
  • script a infinite loop on run.
  • run bean.main with our App()

So, the app will loop until a SIGINT or SIGTERM signal is received. On shutdown, the shutdown method is called, but you can force termination by sending another signal.

from time import sleep
from bean.core import BeanApp, Log, Logger, main, shutdown_requested

class App(BeanApp):
    def startup(self):
        Log.init(
            level=Logger.Level.from_debug(self.DEBUG),
            handlers=[Logger.TermHandler(Logger.fmt(color=True))]
        )
        Log.debug("starting...")

    def shutdown(self):
        Log.debug("ending...")

    def run(self):
        while not shutdown_requested():
            Log.info(":D doing nothing")
            sleep(2)

        Log.warning("xD shutdown requested, exiting run loop")
        return 0

if __name__ == "__main__":
    main(App("bean-app", debug=True))

Config

BeanConfig can be populated from multiple sources, with a defined priority. Available sources:

  • .from_args(): parse command-line arguments with argparse
  • .from_dict(): load configuration from a Python dictionary
  • .from_env(): load environment variables matching prefix + filedname
  • .from_ini(): Load from a INI file
  • .from_json(): load from a JSON file
  • .from_py(): load from a Python file exposing a Config object by default
  • .from_toml(): Load from a TOML file

Note: File loaders will skip missing files unless force=True is passed

from enum import Enum
from bean.core import BeanConfig, ConfigField, isHost, isPort, isEmail

class Color(Enum):
    RED   = "#ff0000"
    GREEN = "#00ff00"
    BLUE  = "#0000ff"

class MyConfig(BeanConfig):
    NAME  = ConfigField(str)
    DEBUG = ConfigField(bool, default=False, short_flag="-d")
    PORT  = ConfigField(int, default=8080, validator=isPort)
    HOST  = ConfigField(str, default="localhost", validator=isHost)
    EMAIL = ConfigField(str, validator=isEmail)
    COLOR = ConfigField(Color, default=Color.RED)

    @BeanConfig.validate("NAME")
    def check_empty_name(name):
        return len(name) > 0


( MyConfig.load()            # load priority:
    .from_env("APP_")        # 1. environment variables
    .from_py("./config.py")  # 2. Python file (ignored if not found)
    .from_args()             # 3. command-line arguments (auto --help)
).build() 

MyConfig.print_config()

Note: If your type checker complains about check_empty_name, add @staticmethod or # type: ignore.

Pipes

Pipe is a small, composable transformation pipeline.

Each stage:

  • Receives a value
  • Returns either:
    • Success(value) or tuple(value, True)
    • tuple(value, ok)
  • If ok == False, the pipeline short-circuits

This makes it easy to build safe, expressive and composable data flows.

from bean.core import Pipe

result = (
    Pipe()
        .guard(lambda x: x != 0)
        .map(lambda x: 10 / x)
)(5)

print(result)       # Success(2.0, ok=True)

Note: pipes can be typed, e.g: Pipe[float, float]()

If the guard fails:

result = (
    Pipe()
        .guard(lambda x: x != 0)
        .map(lambda x: 10 / x)
)(0) 

print(result)       # Success(value=0, ok=False)

The pipe short-circuits and the division step is never executed.

Available Pipe helpers:

  • .map(fn): transform value
  • .guard(fn): validate value (may short-circuit)
  • .peek(fn): side-effect without modifying value
  • .retry(fn, attempts, delay): retry a stage
  • .fallback(fn, fallback_value): recover from failure
  • .branch(cond, success_fn, fail_fn): conditional logic
  • .trigger(fn, ex, msg): raise exception if condition matches

Pipes are fully composable using the | operator.

Shell Commands

Shell commands integrate directly into pipes.

sh(cmd) returns a Pipe that executes a shell command.

from bean.core import sh, cat, tee, stdout

res = ()
print(
    (sh("echo 'hello bean'") 
        | sh("grep -F 'hello'")
        | tee("copy.txt")
        | stdout()
     )(None)
)

Available shell helpers:

  • sh(cmd): run command
  • cat(*paths): read files
  • tee(*paths): write to files
  • stdout(): extract stdout
  • stderr(): extract stderr

Scheduler

Scheduler provides a minimal threaded task runner for delayed and periodic execution.

It supports:

  • One-shot tasks
  • Delayed tasks
  • Periodic jobs
  • Limited or infinite runs
  • Graceful shutdown

Basic example:

from bean.core import Scheduler

( Scheduler()
    .task(lambda: print("Bean task once"))
).start() 

Periodic Job:

from bean.core import Scheduler

schr = (
    Scheduler()
        .job(
            fn=lambda: print("Bean job running!"),
            interval=2.0,
            runs=5
        )
).start()

# ...

schr.join()  # wait until finite jobs complete

Mixed example:

import time
from bean.core import Scheduler

with Scheduler()
    .task(lambda: (print("Init task"), time.sleep(5), print("Done")))
    .job(lambda: print("Heartbeat"), interval=1.0)
) as schr:
    ...

Note: When exiting the with block, the scheduler automatically calls schr.stop().join() to gracefully stop infinite jobs.

API Overview:

  • .task(fn, runs=1, delay=0) -> Schedule a task that runs:

    • Runs runs times (default: once) or indefinitely if runs=None
    • Starts after an optional delay
  • .job(fn, interval, runs=None) -> Schedule a periodic job:

    • Runs every interval seconds
    • Runs indefinitely by default (runs=None)

Lifecycle Methods:

  • .start(): start all tasks
  • .join(timeout=None): wait for completion (non-infinite only)
  • .stop(): signal stop
  • .clear(): remove all tasks

All methods return Self for chaining.

Behavior Notes:

  • Each task runs in its own daemon thread
  • Finite tasks (runs != None) blocks .join() until completion or timeout
  • Infinite tasks (runs=None) are ignored by .join()
  • Tasks must exit voluntarily, blocking calls may delay shutdown
  • Because threads are daemon, all tasks terminate when the main program exits

License

All the repo falls under the MIT 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

bean_core-0.2.0.tar.gz (24.2 kB view details)

Uploaded Source

Built Distribution

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

bean_core-0.2.0-py3-none-any.whl (20.7 kB view details)

Uploaded Python 3

File details

Details for the file bean_core-0.2.0.tar.gz.

File metadata

  • Download URL: bean_core-0.2.0.tar.gz
  • Upload date:
  • Size: 24.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for bean_core-0.2.0.tar.gz
Algorithm Hash digest
SHA256 880ef6ad2188619e00996add87551297395ed3d7ffbcdc541ea73ed448bfdeb0
MD5 543e8cf9b07e8408644d153b4b201252
BLAKE2b-256 7c1ba92acd71189fa97da33a4fce4237d55dfa7533a73e6299d3cd3a99aabc0e

See more details on using hashes here.

File details

Details for the file bean_core-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: bean_core-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 20.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for bean_core-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 35c601e2cfa7c0929104c09678c902f18c4e7865dd1213e691bd2fbf4094ccfc
MD5 71c81484908c9131106f17a90674c44e
BLAKE2b-256 9a3f80d21da2ea4cfcbc217ff97b51285ea3690400a6e6a4751266d87c7cca5c

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