Skip to main content

A flexible, schema-driven library for building dynamic, context-aware content blocks with variable substitution, nested composition, validation, and asynchronous rendering.

Project description

EBlock (Easy Block Builder)

A flexible, schema-driven library for building dynamic, context-aware content blocks with variable substitution, nested composition, validation, and asynchronous rendering.

Designed for use in page builders, CMS backends, or dynamic UI generation systems where content is defined as structured JSON, rendered with context variables, and validated via Pydantic models.


Installation

pip install eblock

or with uv:

uv add eblock

Core Concepts

Context

A container for variables and metadata used during block rendering.

  • Variables are case-normalized to uppercase.
  • Supports deep copying for safety.
  • Allows dynamic assignment via ctx["key"] = value.
  • Stores:
    • vars: dictionary of scalar or structured values (strings, numbers, lists, dicts, etc.)
    • extra: arbitrary metadata (not used in substitution)

BaseBlock

Abstract base class for all content blocks.

Each block: - Accepts properties (dict) on initialization. - Optionally validates input/output using Pydantic models. - Recursively resolves {{ variable }} placeholders from Context. - Supports nesting inside dicts, lists, tuples, and sets. - Exposes an async build() method returning (result_dict, output_schema). - Allows custom output transformation via prepare_output().


Block Registration Mechanism

EBlock uses an automatic class-based registration system powered by the BlockMeta metaclass.

How It Works

Every subclass of BaseBlock that defines a _type attribute is automatically registered on import.

class TextBlock(BaseBlock):
    _type = "text"

When Python loads this class:

  • BlockMeta.__new__ is triggered.
  • If _type is not "base" and not private (_SomethingBlock), the block is added to the global registry.
  • Duplicate _type values produce a warning and are ignored.

Accessing Registered Blocks

You can list all registered block types:

from eblock import get_registered_block_types

print(get_registered_block_types())
# ["text", "score_card", ...]

Creating Blocks Dynamically

Use the factory function:

from eblock import create_block

block = create_block("text", {"content": "Hello"})

Building Blocks from JSON-like Config

The library supports nested block trees:

from eblock import create_blocks_from_config

blocks = create_blocks_from_config([
    {"type": "text", "properties": {"content": "Hi"}},
    {"type": "score_card", "properties": {"title": "Test", "score": 95}},
])

Nested blocks inside dicts/lists are resolved recursively.

Why Use Automatic Registration?

  • No need for manual registries.
  • Blocks register themselves as soon as they are imported.
  • Decoupled plugins/modules can define their own block types.
  • Ensures consistency when building from JSON configs.

Features

Variable substitution
Placeholders like {{ user_name }} are replaced with values from context.

Recursive structure support
Handles dicts, lists, tuples, sets, and nested BaseBlock instances.

Input/Output validation
Define _input_schema and _output_schema using Pydantic models.

Computed fields
Override prepare_output() to modify or add fields before output validation.

Static dependency analysis
Use .get_vars() to extract all required variables, including in nested blocks.

Async-first design
Full async/await support throughout the rendering pipeline.

Structured logging
Blocks and context instances log operations through hierarchical loggers.


Usage Examples

Basic Block

from eblock import BaseBlock, Context

class TextBlock(BaseBlock):
    _type = "text"

ctx = Context(vars={"name": "Alice"})
block = TextBlock({"content": "Hello, {{ name }}!"})
result, _ = await block.build(ctx)
# result == {"content": "Hello, Alice!"}

Validated Block with Computed Output

from pydantic import BaseModel
from eblock import BaseBlock

class Input(BaseModel):
    title: str
    score: int

class Output(BaseModel):
    title: str
    score: int
    badge: str

class ScoreCard(BaseBlock):
    _type = "score_card"
    _input_schema = Input
    _output_schema = Output

    async def prepare_output(self, result, ctx):
        result["badge"] = "🏆" if result["score"] >= 90 else "📝"
        return result

Nested Blocks

header = TextBlock({"text": "Welcome, {{ user }}!"})
page = PageBlock({"header": header, "theme": "{{ theme }}"})
# Both variables will be resolved from context

Variable Inspection

block = TextBlock({"msg": "Hi {{ first }}, {{ last }}!"})
print(block.get_vars())  # {"FIRST", "LAST"}

API Reference

class Context

  • __init__(self, vars: dict | None = None, **extra)
  • __getitem__(key)
  • __setitem__(key, value)
  • Properties:
    • vars --- deep copy of stored variables\
    • extra --- shallow copy of extra data

class BaseBlock

  • __init__(self, properties: dict)
  • async build(self, ctx: Context) -> tuple[dict, Type[BaseModel] | None]
  • async prepare_output(self, result: dict, ctx: Context) -> dict
  • get_vars(self) -> set[str]
  • Property: type --- block identifier

Define in subclasses

  • _type: str --- identifier (required)
  • _input_schema: Type[BaseModel] | None
  • _output_schema: Type[BaseModel] | None

Registry Helpers

  • create_block(type, properties)
  • create_blocks_from_config(config)
  • get_registered_block_types()

Requirements

  • Python ≥ 3.10
  • Pydantic ≥ 2.0

Notes

  • Placeholders must use double curly braces: {{ var_name }}.
  • Whitespace is trimmed: {{ user }}user.
  • Missing variables resolve to empty string and produce a warning.
  • Blocks should be treated as stateless and recreated when reused.

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

eblock-0.3.0.tar.gz (20.3 kB view details)

Uploaded Source

Built Distribution

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

eblock-0.3.0-py3-none-any.whl (13.2 kB view details)

Uploaded Python 3

File details

Details for the file eblock-0.3.0.tar.gz.

File metadata

  • Download URL: eblock-0.3.0.tar.gz
  • Upload date:
  • Size: 20.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for eblock-0.3.0.tar.gz
Algorithm Hash digest
SHA256 206f83b6b02cc0938527ef693fedb69fb44fdc6d031d2117b49a872f9f33d889
MD5 5a53fe71352aafd1563ff65961662a29
BLAKE2b-256 1f62c7b4ca5a7217ae70e809d493a5f4262f1ee4b610b6b25f26fc9b77210288

See more details on using hashes here.

Provenance

The following attestation bundles were made for eblock-0.3.0.tar.gz:

Publisher: python-publish.yml on GAKiknadze/easy_block_builder

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file eblock-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: eblock-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 13.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for eblock-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 acd48586b56dc6ba79b79a218867b46bdd9a37aad8b4e78f58a22582972d788a
MD5 e66458bd43421f6e9c8f1233fe593ecf
BLAKE2b-256 ffed80ace570de9d5928e7a0992a97ceb137b6d679a722486b233c492df79973

See more details on using hashes here.

Provenance

The following attestation bundles were made for eblock-0.3.0-py3-none-any.whl:

Publisher: python-publish.yml on GAKiknadze/easy_block_builder

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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