# Kida 0.2.0 URL: /releases/0.2.0/ Section: releases Tags: release, changelog, streaming, async, loaders, analysis, error-messages Date: 2026-02-08 -------------------------------------------------------------------------------- v0.2.0 Released: February 8, 2026 Streaming rendering for both sync and async, four new loaders for flexible template architectures, a public static analysis API, and significantly improved error messages. Highlights Streaming rendering — render_stream() and render_stream_async() yield chunks as they're produced Async-first — AsyncLoopContext, render_block_stream_async(), Template.is_async, async_render_context() Four new loaders — ChoiceLoader, PrefixLoader, PackageLoader, FunctionLoader Static analysis — validate_context() catches missing variables before render Better errors — Variable name suggestions, source snippets, template name suggestions 48% faster cold-start — Lazy analysis imports avoid eagerly loading AST definitions Added Streaming Rendering template.render_stream(**ctx) yields template output as string chunks via Python generators: from kida import Environment env = Environment(loader=...) template = env.get_template("report.html") for chunk in template.render_stream(items=data): send_to_client(chunk) The compiler generates both StringBuilder (render()) and generator (render_stream()) functions in a single compilation pass. Full support for template inheritance, includes, and all control flow. Buffering blocks ({% capture %}, {% spaceless %}, {% cache %}, {% filter %}) buffer internally and yield the processed result. Zero performance impact on the existing render() path. Native Async Streaming template.render_stream_async(**ctx) yields chunks via async generators: async for chunk in template.render_stream_async(items=data): await response.write(chunk) Supports {% async for %} over async iterables, {{ await expr }} for inline coroutine resolution, and {% empty %} fallback clauses. Async child templates can extend sync parents seamlessly. RenderedTemplate Lazy iterable wrapper around render_stream(): from kida import RenderedTemplate rendered = RenderedTemplate(template, {"items": data}) for chunk in rendered: send_to_client(chunk) AsyncLoopContext Loop variable (loop) for {% async for %}. Provides index-forward properties (index, index0, first, previtem, cycle()). Size-dependent properties (last, length, revindex) raise TemplateRuntimeError since async iterables have no known length. Four New Loaders ChoiceLoader — Try multiple loaders in order (theme fallback): from kida import ChoiceLoader, FileSystemLoader loader = ChoiceLoader([ FileSystemLoader("themes/custom/"), FileSystemLoader("themes/default/"), ]) PrefixLoader — Namespace templates by prefix (plugin architectures): from kida import PrefixLoader, FileSystemLoader, DictLoader loader = PrefixLoader({ "app": FileSystemLoader("templates/app/"), "admin": FileSystemLoader("templates/admin/"), "shared": DictLoader({"header.html": "<header/>"}), }) env.get_template("app/index.html") # Delegates to app loader PackageLoader — Load from installed Python packages via importlib.resources: from kida import PackageLoader loader = PackageLoader("my_framework", "templates") FunctionLoader — Wrap any callable as a loader: from kida import FunctionLoader loader = FunctionLoader(lambda name: my_db.get_template(name)) Static Analysis API Pre-render variable validation and template introspection: from kida import Environment, AnalysisConfig env = Environment(loader=...) template = env.get_template("page.html") # Check for missing variables before rendering errors = template.validate_context({"title": "Hello"}) for error in errors: print(error) # "Missing variable: 'items'" AnalysisConfig, BlockMetadata, and TemplateMetadata are exported from kida (lazy-loaded to avoid cold-start penalty). Compiler-Emitted Profiling profiled_render() now automatically tracks blocks, filters, and macros without manual instrumentation: from kida import profiled_render with profiled_render() as metrics: html = template.render(page=page) print(metrics.summary()) # Blocks with timing, filter call counts, macro call counts Zero overhead when profiling is disabled. Other Additions Include scope propagation — Loop variables from {% for %} and block-scoped {% set %} are now visible inside {% include %} templates Bytecode cache warning — from_string() without name= emits a UserWarning when a bytecode_cache is configured *args/**kwargs in {% def %} — Template-defined functions accept variadic arguments Template.is_async — Boolean property for detecting async templates; render() raises on async templates async_render_context() — Async context manager matching the sync render_context() API render_block_stream_async() — Render a single block as an async stream Changed Dict-Safe Attribute Resolution _safe_getattr now tries subscript before getattr for dict objects: {{ section.items }} {# Now resolves to section["items"] (user data), not dict.items method #} This prevents dict method names (items, keys, values, get, pop, update) from shadowing user data keys. Non-dict objects retain the previous getattr-first behavior. Better Error Messages UndefinedError suggests similar variable names via fuzzy matching TemplateSyntaxError includes source snippets with surrounding line context DictLoader suggests similar template names on miss Bare RuntimeErrors include template name and line number Performance 48% faster cold-start — AnalysisConfig, BlockMetadata, and TemplateMetadata are lazy-loaded via __getattr__, avoiding eager import of 974 lines of AST definitions CI: mypy replaced with ty — All type checking now uses Astral's Rust-based ty checker Internal Improvements Compiler mixin extraction — CachingMixin, WithBlockMixin, and PatternMatchingMixin extracted from monolithic compiler modules template.py split into template/ package — 1,277-line module split into core.py, helpers.py, introspection.py, loop_context.py, cached_blocks.py Narrowed type annotations — Specific subclasses replace broad Node types in visitor methods; Any types tightened across compiler and template modules Sorted __all__ — Public API exports alphabetically sorted Upgrade Guide No breaking changes — All existing render() calls continue to work unchanged Dict attribute resolution — If you relied on {{ my_dict.items }} calling dict.items(), use the items filter instead: {{ my_dict | items }} New async guard — Templates containing {% async for %} or {{ await }} will now raise TemplateRuntimeError if called with sync render(). Use render_async() or render_stream_async(). Links PyPI GitHub Full Changelog -------------------------------------------------------------------------------- Metadata: - Word Count: 781 - Reading Time: 4 minutes