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.

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.update(handlers=Logger.TermHandler(Logger.fmt_color))
        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:

  • .load_args(): parse command-line arguments with argparse
  • .load_dict(): load configuration from a Python dictionary
  • .load_env(): load environment variables matching prefix + filedname
  • .load_ini(): Load from a INI file
  • .load_json(): load from a JSON file
  • .load_py(): load from a Python file exposing a Config object by default
  • .load_toml(): Load from a TOML file

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

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

class MyConfig(BeanConfig):
    NAME: str = ConfigField(str)
    PORT: int = ConfigField(int, default=8080, validator=isPort)
    HOST: str = ConfigField(str, default="localhost", validator=isHost)
    EMAIL: str = ConfigField(str, validator=isEmail)

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


( MyConfig.load()            # load priority:
    .load_env("APP_")        # 1. environment variables
    .load_py("./config.py")  # 2. Python file (ignored if not found)
    .load_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.1.0.tar.gz (21.1 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.1.0-py3-none-any.whl (17.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: bean_core-0.1.0.tar.gz
  • Upload date:
  • Size: 21.1 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.1.0.tar.gz
Algorithm Hash digest
SHA256 b14110af3e7451052c3f8daa594b3bad0d83e88e9c77b258fdc176a47d1b7500
MD5 e489913771b2f8a13f0d3a8dff2fa9b9
BLAKE2b-256 29061bee8d6a8f315ea20b0d8142631861cd6bcfaa50d4681409f3b0a578c390

See more details on using hashes here.

File details

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

File metadata

  • Download URL: bean_core-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.9 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 878ba9d8756b065c8a8b8046dfd346071cd72a2a0b9c9d95cfe5e2c76814f6c6
MD5 7ccd3bbf4a2a16f0b26dcb863891778f
BLAKE2b-256 140d46af8899fe3dd3ff61fdd43722fd7dd117ea53752b77f29e6e33cb5b1b64

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