Skip to main content

A reusable tkinter/ttkbootstrap UI framework

Project description

Jabs Mimir

Jabs Mimir is a lightweight, extensible UI micro-framework built on top of tkinter and ttkbootstrap, designed for rapid internal tool development and structured form workflows.

It provides:

  • Reusable UI primitives with validation and tooltips
  • Support for block-based form components with dynamic variable binding
  • Integration with custom validation logic
  • Modular architecture suitable for internal tooling and small boilerplate projects

Installation

pip install jabs-mimir

Quick Start

from jabs_mimir import Mimir, DataBlockWrapper, UtilityModule
import tkinter as tk
import ttkbootstrap as tb

class App(tb.Window):
    def __init__(self):
        super().__init__(title="Mimir Demo")
        self.ui = Mimir(self)
        self.ui.setValidatorResolver(lambda name: {"not_empty": lambda val: bool(val.strip())}.get(name))

        self.ui.switchView(self.mainView)

    def mainView(self, ui, *args):
        frame = tb.Frame(self)
        fields = [
            {"type": "heading", "label": "Basic Info"},
            {"label": "Name", "key": "name", "variable": tk.StringVar(), "validation": "not_empty"},
            {"label": "Age", "key": "age", "variable": tk.IntVar()}
        ]

        meta = UtilityModule.buildBlockMeta(fields)
        block = DataBlockWrapper(meta)

        ui.renderFields(frame, fields)
        ui.addNextButton(frame, row=len(fields)+1, command=lambda: print(UtilityModule.getBlockValues(block)))

        return frame

if __name__ == "__main__":
    app = App()
    app.mainloop()

Components

Mimir

Manages UI views, tooltips, validation, field rendering, and form logic.

DataBlockWrapper

A wrapper for block-level form metadata and values. Supports dot-access and .get()/.set() calls.

UtilityModule

Helper methods for building field metadata, extracting values, validating blocks, etc.


License

MIT License © 2025 William Lydahl

📘 API Documentation

<!doctype html>

<title>jabs_mimir API documentation</title>
<style>/*! * Bootstrap Reboot v5.0.0 (https://getbootstrap.com/) * Copyright 2011-2021 The Bootstrap Authors * Copyright 2011-2021 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}</style>
<style>/*! syntax-highlighting.css */pre{line-height:125%;}span.linenos{color:inherit; background-color:transparent; padding-left:5px; padding-right:20px;}.pdoc-code .hll{background-color:#ffffcc}.pdoc-code{background:#f8f8f8;}.pdoc-code .c{color:#3D7B7B; font-style:italic}.pdoc-code .err{border:1px solid #FF0000}.pdoc-code .k{color:#008000; font-weight:bold}.pdoc-code .o{color:#666666}.pdoc-code .ch{color:#3D7B7B; font-style:italic}.pdoc-code .cm{color:#3D7B7B; font-style:italic}.pdoc-code .cp{color:#9C6500}.pdoc-code .cpf{color:#3D7B7B; font-style:italic}.pdoc-code .c1{color:#3D7B7B; font-style:italic}.pdoc-code .cs{color:#3D7B7B; font-style:italic}.pdoc-code .gd{color:#A00000}.pdoc-code .ge{font-style:italic}.pdoc-code .gr{color:#E40000}.pdoc-code .gh{color:#000080; font-weight:bold}.pdoc-code .gi{color:#008400}.pdoc-code .go{color:#717171}.pdoc-code .gp{color:#000080; font-weight:bold}.pdoc-code .gs{font-weight:bold}.pdoc-code .gu{color:#800080; font-weight:bold}.pdoc-code .gt{color:#0044DD}.pdoc-code .kc{color:#008000; font-weight:bold}.pdoc-code .kd{color:#008000; font-weight:bold}.pdoc-code .kn{color:#008000; font-weight:bold}.pdoc-code .kp{color:#008000}.pdoc-code .kr{color:#008000; font-weight:bold}.pdoc-code .kt{color:#B00040}.pdoc-code .m{color:#666666}.pdoc-code .s{color:#BA2121}.pdoc-code .na{color:#687822}.pdoc-code .nb{color:#008000}.pdoc-code .nc{color:#0000FF; font-weight:bold}.pdoc-code .no{color:#880000}.pdoc-code .nd{color:#AA22FF}.pdoc-code .ni{color:#717171; font-weight:bold}.pdoc-code .ne{color:#CB3F38; font-weight:bold}.pdoc-code .nf{color:#0000FF}.pdoc-code .nl{color:#767600}.pdoc-code .nn{color:#0000FF; font-weight:bold}.pdoc-code .nt{color:#008000; font-weight:bold}.pdoc-code .nv{color:#19177C}.pdoc-code .ow{color:#AA22FF; font-weight:bold}.pdoc-code .w{color:#bbbbbb}.pdoc-code .mb{color:#666666}.pdoc-code .mf{color:#666666}.pdoc-code .mh{color:#666666}.pdoc-code .mi{color:#666666}.pdoc-code .mo{color:#666666}.pdoc-code .sa{color:#BA2121}.pdoc-code .sb{color:#BA2121}.pdoc-code .sc{color:#BA2121}.pdoc-code .dl{color:#BA2121}.pdoc-code .sd{color:#BA2121; font-style:italic}.pdoc-code .s2{color:#BA2121}.pdoc-code .se{color:#AA5D1F; font-weight:bold}.pdoc-code .sh{color:#BA2121}.pdoc-code .si{color:#A45A77; font-weight:bold}.pdoc-code .sx{color:#008000}.pdoc-code .sr{color:#A45A77}.pdoc-code .s1{color:#BA2121}.pdoc-code .ss{color:#19177C}.pdoc-code .bp{color:#008000}.pdoc-code .fm{color:#0000FF}.pdoc-code .vc{color:#19177C}.pdoc-code .vg{color:#19177C}.pdoc-code .vi{color:#19177C}.pdoc-code .vm{color:#19177C}.pdoc-code .il{color:#666666}</style>
<style>/*! theme.css */:root{--pdoc-background:#fff;}.pdoc{--text:#212529;--muted:#6c757d;--link:#3660a5;--link-hover:#1659c5;--code:#f8f8f8;--active:#fff598;--accent:#eee;--accent2:#c1c1c1;--nav-hover:rgba(255, 255, 255, 0.5);--name:#0066BB;--def:#008800;--annotation:#007020;}</style>
<style>/*! layout.css */html, body{width:100%;height:100%;}html, main{scroll-behavior:smooth;}body{background-color:var(--pdoc-background);}@media (max-width:769px){#navtoggle{cursor:pointer;position:absolute;width:50px;height:40px;top:1rem;right:1rem;border-color:var(--text);color:var(--text);display:flex;opacity:0.8;z-index:999;}#navtoggle:hover{opacity:1;}#togglestate + div{display:none;}#togglestate:checked + div{display:inherit;}main, header{padding:2rem 3vw;}header + main{margin-top:-3rem;}.git-button{display:none !important;}nav input[type="search"]{max-width:77%;}nav input[type="search"]:first-child{margin-top:-6px;}nav input[type="search"]:valid ~ *{display:none !important;}}@media (min-width:770px){:root{--sidebar-width:clamp(12.5rem, 28vw, 22rem);}nav{position:fixed;overflow:auto;height:100vh;width:var(--sidebar-width);}main, header{padding:3rem 2rem 3rem calc(var(--sidebar-width) + 3rem);width:calc(54rem + var(--sidebar-width));max-width:100%;}header + main{margin-top:-4rem;}#navtoggle{display:none;}}#togglestate{position:absolute;height:0;opacity:0;}nav.pdoc{--pad:clamp(0.5rem, 2vw, 1.75rem);--indent:1.5rem;background-color:var(--accent);border-right:1px solid var(--accent2);box-shadow:0 0 20px rgba(50, 50, 50, .2) inset;padding:0 0 0 var(--pad);overflow-wrap:anywhere;scrollbar-width:thin; scrollbar-color:var(--accent2) transparent; z-index:1}nav.pdoc::-webkit-scrollbar{width:.4rem; }nav.pdoc::-webkit-scrollbar-thumb{background-color:var(--accent2); }nav.pdoc > div{padding:var(--pad) 0;}nav.pdoc .module-list-button{display:inline-flex;align-items:center;color:var(--text);border-color:var(--muted);margin-bottom:1rem;}nav.pdoc .module-list-button:hover{border-color:var(--text);}nav.pdoc input[type=search]{display:block;outline-offset:0;width:calc(100% - var(--pad));}nav.pdoc .logo{max-width:calc(100% - var(--pad));max-height:35vh;display:block;margin:0 auto 1rem;transform:translate(calc(-.5 * var(--pad)), 0);}nav.pdoc ul{list-style:none;padding-left:0;}nav.pdoc > div > ul{margin-left:calc(0px - var(--pad));}nav.pdoc li a{padding:.2rem 0 .2rem calc(var(--pad) + var(--indent));}nav.pdoc > div > ul > li > a{padding-left:var(--pad);}nav.pdoc li{transition:all 100ms;}nav.pdoc li:hover{background-color:var(--nav-hover);}nav.pdoc a, nav.pdoc a:hover{color:var(--text);}nav.pdoc a{display:block;}nav.pdoc > h2:first-of-type{margin-top:1.5rem;}nav.pdoc .class:before{content:"class ";color:var(--muted);}nav.pdoc .function:after{content:"()";color:var(--muted);}nav.pdoc footer:before{content:"";display:block;width:calc(100% - var(--pad));border-top:solid var(--accent2) 1px;margin-top:1.5rem;padding-top:.5rem;}nav.pdoc footer{font-size:small;}</style>
<style>/*! content.css */.pdoc{color:var(--text);box-sizing:border-box;line-height:1.5;background:none;}.pdoc .pdoc-button{cursor:pointer;display:inline-block;border:solid black 1px;border-radius:2px;font-size:.75rem;padding:calc(0.5em - 1px) 1em;transition:100ms all;}.pdoc .alert{padding:1rem 1rem 1rem calc(1.5rem + 24px);border:1px solid transparent;border-radius:.25rem;background-repeat:no-repeat;background-position:.75rem center;margin-bottom:1rem;}.pdoc .alert > em{display:none;}.pdoc .alert > *:last-child{margin-bottom:0;}.pdoc .alert.note {color:#084298;background-color:#cfe2ff;border-color:#b6d4fe;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22%23084298%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M8%2016A8%208%200%201%200%208%200a8%208%200%200%200%200%2016zm.93-9.412-1%204.705c-.07.34.029.533.304.533.194%200%20.487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703%200-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381%202.29-.287zM8%205.5a1%201%200%201%201%200-2%201%201%200%200%201%200%202z%22/%3E%3C/svg%3E");}.pdoc .alert.warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22%23664d03%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M8.982%201.566a1.13%201.13%200%200%200-1.96%200L.165%2013.233c-.457.778.091%201.767.98%201.767h13.713c.889%200%201.438-.99.98-1.767L8.982%201.566zM8%205c.535%200%20.954.462.9.995l-.35%203.507a.552.552%200%200%201-1.1%200L7.1%205.995A.905.905%200%200%201%208%205zm.002%206a1%201%200%201%201%200%202%201%201%200%200%201%200-2z%22/%3E%3C/svg%3E");}.pdoc .alert.danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22%23842029%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M5.52.359A.5.5%200%200%201%206%200h4a.5.5%200%200%201%20.474.658L8.694%206H12.5a.5.5%200%200%201%20.395.807l-7%209a.5.5%200%200%201-.873-.454L6.823%209.5H3.5a.5.5%200%200%201-.48-.641l2.5-8.5z%22/%3E%3C/svg%3E");}.pdoc .visually-hidden{position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important;}.pdoc h1, .pdoc h2, .pdoc h3{font-weight:300;margin:.3em 0;padding:.2em 0;}.pdoc > section:not(.module-info) h1{font-size:1.5rem;font-weight:500;}.pdoc > section:not(.module-info) h2{font-size:1.4rem;font-weight:500;}.pdoc > section:not(.module-info) h3{font-size:1.3rem;font-weight:500;}.pdoc > section:not(.module-info) h4{font-size:1.2rem;}.pdoc > section:not(.module-info) h5{font-size:1.1rem;}.pdoc a{text-decoration:none;color:var(--link);}.pdoc a:hover{color:var(--link-hover);}.pdoc blockquote{margin-left:2rem;}.pdoc pre{border-top:1px solid var(--accent2);border-bottom:1px solid var(--accent2);margin-top:0;margin-bottom:1em;padding:.5rem 0 .5rem .5rem;overflow-x:auto;background-color:var(--code);}.pdoc code{color:var(--text);padding:.2em .4em;margin:0;font-size:85%;background-color:var(--accent);border-radius:6px;}.pdoc a > code{color:inherit;}.pdoc pre > code{display:inline-block;font-size:inherit;background:none;border:none;padding:0;}.pdoc > section:not(.module-info){margin-bottom:1.5rem;}.pdoc .modulename{margin-top:0;font-weight:bold;}.pdoc .modulename a{color:var(--link);transition:100ms all;}.pdoc .git-button{float:right;border:solid var(--link) 1px;}.pdoc .git-button:hover{background-color:var(--link);color:var(--pdoc-background);}.view-source-toggle-state,.view-source-toggle-state ~ .pdoc-code{display:none;}.view-source-toggle-state:checked ~ .pdoc-code{display:block;}.view-source-button{display:inline-block;float:right;font-size:.75rem;line-height:1.5rem;color:var(--muted);padding:0 .4rem 0 1.3rem;cursor:pointer;text-indent:-2px;}.view-source-button > span{visibility:hidden;}.module-info .view-source-button{float:none;display:flex;justify-content:flex-end;margin:-1.2rem .4rem -.2rem 0;}.view-source-button::before{position:absolute;content:"View Source";display:list-item;list-style-type:disclosure-closed;}.view-source-toggle-state:checked ~ .attr .view-source-button::before,.view-source-toggle-state:checked ~ .view-source-button::before{list-style-type:disclosure-open;}.pdoc .docstring{margin-bottom:1.5rem;}.pdoc section:not(.module-info) .docstring{margin-left:clamp(0rem, 5vw - 2rem, 1rem);}.pdoc .docstring .pdoc-code{margin-left:1em;margin-right:1em;}.pdoc h1:target,.pdoc h2:target,.pdoc h3:target,.pdoc h4:target,.pdoc h5:target,.pdoc h6:target,.pdoc .pdoc-code > pre > span:target{background-color:var(--active);box-shadow:-1rem 0 0 0 var(--active);}.pdoc .pdoc-code > pre > span:target{display:block;}.pdoc div:target > .attr,.pdoc section:target > .attr,.pdoc dd:target > a{background-color:var(--active);}.pdoc *{scroll-margin:2rem;}.pdoc .pdoc-code .linenos{user-select:none;}.pdoc .attr:hover{filter:contrast(0.95);}.pdoc section, .pdoc .classattr{position:relative;}.pdoc .headerlink{--width:clamp(1rem, 3vw, 2rem);position:absolute;top:0;left:calc(0rem - var(--width));transition:all 100ms ease-in-out;opacity:0;}.pdoc .headerlink::before{content:"#";display:block;text-align:center;width:var(--width);height:2.3rem;line-height:2.3rem;font-size:1.5rem;}.pdoc .attr:hover ~ .headerlink,.pdoc *:target > .headerlink,.pdoc .headerlink:hover{opacity:1;}.pdoc .attr{display:block;margin:.5rem 0 .5rem;padding:.4rem .4rem .4rem 1rem;background-color:var(--accent);overflow-x:auto;}.pdoc .classattr{margin-left:2rem;}.pdoc .decorator-deprecated{color:#842029;}.pdoc .decorator-deprecated ~ span{filter:grayscale(1) opacity(0.8);}.pdoc .name{color:var(--name);font-weight:bold;}.pdoc .def{color:var(--def);font-weight:bold;}.pdoc .signature{background-color:transparent;}.pdoc .param, .pdoc .return-annotation{white-space:pre;}.pdoc .signature.multiline .param{display:block;}.pdoc .signature.condensed .param{display:inline-block;}.pdoc .annotation{color:var(--annotation);}.pdoc .view-value-toggle-state,.pdoc .view-value-toggle-state ~ .default_value{display:none;}.pdoc .view-value-toggle-state:checked ~ .default_value{display:inherit;}.pdoc .view-value-button{font-size:.5rem;vertical-align:middle;border-style:dashed;margin-top:-0.1rem;}.pdoc .view-value-button:hover{background:white;}.pdoc .view-value-button::before{content:"show";text-align:center;width:2.2em;display:inline-block;}.pdoc .view-value-toggle-state:checked ~ .view-value-button::before{content:"hide";}.pdoc .inherited{margin-left:2rem;}.pdoc .inherited dt{font-weight:700;}.pdoc .inherited dt, .pdoc .inherited dd{display:inline;margin-left:0;margin-bottom:.5rem;}.pdoc .inherited dd:not(:last-child):after{content:", ";}.pdoc .inherited .class:before{content:"class ";}.pdoc .inherited .function a:after{content:"()";}.pdoc .search-result .docstring{overflow:auto;max-height:25vh;}.pdoc .search-result.focused > .attr{background-color:var(--active);}.pdoc .attribution{margin-top:2rem;display:block;opacity:0.5;transition:all 200ms;filter:grayscale(100%);}.pdoc .attribution:hover{opacity:1;filter:grayscale(0%);}.pdoc .attribution img{margin-left:5px;height:35px;vertical-align:middle;width:70px;transition:all 200ms;}.pdoc table{display:block;width:max-content;max-width:100%;overflow:auto;margin-bottom:1rem;}.pdoc table th{font-weight:600;}.pdoc table th, .pdoc table td{padding:6px 13px;border:1px solid var(--accent2);}</style>
<style>/*! custom.css */</style></head>

jabs_mimir

                    <input id="mod-jabs_mimir-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">

                    <label class="view-source-button" for="mod-jabs_mimir-view-source"><span>View Source</span></label>

                    <div class="pdoc-code codehilite"><pre><span></span><span id="L-1"><a href="#L-1"><span class="linenos">1</span></a><span class="kn">from</span><span class="w"> </span><span class="nn">.Mimir</span><span class="w"> </span><span class="kn">import</span> <span class="n">Mimir</span>

2from .DataBlockWrapper import DataBlockWrapper 3from .UtilityModule import UtilityModule 4 5all = ["Mimir", "DataBlockWrapper", "UtilityModule"]

        </section>
            <section id="Mimir">
                        <input id="Mimir-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
<span class="def">class</span>
<span class="name">Mimir</span>:

            <label class="view-source-button" for="Mimir-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir-16"><a href="#Mimir-16"><span class="linenos"> 16</span></a><span class="k">class</span><span class="w"> </span><span class="nc">Mimir</span><span class="p">:</span>

17 widthEntry = 20 18 widthCombo = widthEntry - 2 19 20 def init(self, app): 21 """ 22 Initialize Mimir with reference to the root app window. 23 """ 24 self.app = app 25 self.currentView = None 26 self.invalidEntries = set() 27 self._validatorResolver = lambda name: None 28 self.fieldRenderers = { 29 "entry": self._renderEntryField, 30 "combobox": self._renderComboboxField, 31 "heading": self._renderHeading, 32 } 33 self._allowThemeToggle = False 34 35 def allowDarkMode(self, enabled=True): 36 """Enable or disable theme toggle button in the top right.""" 37 self._allowThemeToggle = enabled 38 39 def _toggleTheme(self): 40 """Switch between light and dark themes.""" 41 current = self.app.style.theme.name 42 self.app.style.theme_use("darkly" if current != "darkly" else "cosmo") 43 44 def setValidatorResolver(self, resolverFunc): 45 """Register a function that returns validation callbacks by name.""" 46 self._validatorResolver = resolverFunc 47 48 def switchView(self, newFrameFunc, gridOptions=None, kwargs): 49 """ 50 Replace the current view with a new frame built by newFrameFunc. 51 Automatically configures geometry and grid placement. 52 """
53 if self.currentView: 54 self.currentView.destroy() 55 56 container = tb.Frame(self.app) 57 container.grid(row=0, column=0, sticky="nsew") 58 container.columnconfigure(0, weight=1) 59 60 topBar = tb.Frame(container) 61 topBar.grid(row=0, column=0, sticky="ew") 62 63 if self._allowThemeToggle: 64 tb.Button(topBar, text="Theme", command=self._toggleTheme).pack(side="right", padx=10, pady=10) 65 66 newFrame = newFrameFunc(self,
kwargs) 67 options = gridOptions or {"row": 1, "column": 0, "sticky": "n", "padx": 50, "pady": 20} 68 newFrame.grid(options) 69 self.app.rowconfigure(options.get("row", 0), weight=1) 70 self.app.columnconfigure(options.get("column", 0), weight=1) 71 self.currentView = newFrame 72 self.app.update_idletasks() 73 self.app.geometry("") 74 return newFrame 75 76 def popupView(self, viewFunc, title="Popup", size="fit", width=500, height=400, modal=False): 77 """ 78 Open a popup window and embed a view frame into it. 79 If modal=True, interaction with other windows is blocked. 80 """
81 popup = tk.Toplevel(self.app) 82 popup.title(title) 83 popup.transient(self.app) 84 popup.resizable(False, False) 85 86 if size == "fit": 87 popup.update_idletasks() 88 popup.geometry("") 89 else: 90 popup.geometry(f"{width}x{height}") 91 92 popup.grid_rowconfigure(0, weight=1) 93 popup.grid_columnconfigure(0, weight=1) 94 viewFrame = viewFunc(self, popup) 95 viewFrame.grid(row=0, column=0, sticky="nsew") 96 97 if modal: 98 popup.grab_set() 99 popup.focus_set() 100 101 return popup 102 103 def renderBlockUI(self, container, fields, blockList, layout="horizontal", meta=None, label=None, noRemove=False): 104 """ 105 Render a labeled input block with specified fields and layout style. 106 Automatically attaches the block to blockList and handles UI labels and deletion logic. 107 """
108 index = len(blockList) + 1 109 labelText = label or f"Block {index}" 110 frame = tb.LabelFrame(container, text=labelText) 111 frame.configure(labelanchor="n") 112
113 if layout == "vertical": 114 frame.grid(row=index - 1, column=0, padx=10, pady=10, sticky="w") 115 else: 116 frame.grid(row=0, column=index - 1, padx=10, pady=10, sticky="n") 117 118 boldFont = self.getCurrentFont() 119 boldFont.configure(weight="bold") 120 121 if meta is None: 122 meta = {} 123 if "custom_label" not in meta and label: 124 meta["custom_label"] = label 125 126 blockMeta = { 127 "frame": frame, 128 "fields": fields, 129 "fields_by_key": {f["key"]: f["variable"] for f in fields if "key" in f}, 130 "layout": layout, 131
meta 132 } 133 134 135 wrapper = DataBlockWrapper(blockMeta) 136 blockList.append(wrapper) 137 138 row = 0 139 for field in fields: 140 ftype = field.get("type", "entry") 141 renderer = self.fieldRenderers.get(ftype) 142 if renderer: 143 renderer(frame, row, field) 144 145 # Adjust row offset based on field type 146 if ftype == "fileupload": 147 row += 3 148 elif ftype == "heading": 149 row += 1 150 else: 151 row += 2 152 153 if not noRemove: 154 removeBtn = tb.Button( 155 frame, 156 text="🗑️ Ta bort", 157 bootstyle="danger-outline", 158 command=lambda w=wrapper: self.removeBlock(blockList, w) 159 ) 160 removeBtn.grid(row=row, column=0, columnspan=2, pady=(10, 5)) 161 162 return wrapper 163 164 165 def registerFieldType(self, name, renderer): 166 """Register a custom field renderer by name."""
167 self.fieldRenderers[name] = renderer 168 169 def _renderEntryField(self, parent, row, field): 170 """Internal: Render an entry field with label.""" 171 self.addLabeledEntry( 172 parent=parent, 173 row=row, 174 label=field["label"], 175 variable=field["variable"], 176 validation=field.get("validation"), 177 field.get("options", {}) # <-- wildcard pass-through 178 ) 179 180 def _renderComboboxField(self, parent, row, field): 181 """Internal: Render a combobox field with label.""" 182 self.addLabeledCombobox( 183 parent=parent, 184 row=row, 185 label=field["label"], 186 variable=field["variable"], 187 values=field["values"], 188 field.get("options", {}) # <-- pass all UI customization 189 ) 190 def _renderHeading(self, parent, row, field): 191 """Internal: Render a bold heading label within a form.""" 192 font = self.getCurrentFont() 193 font.configure(weight="bold") 194 195 align = field.get("options", {}).get("align", "w") # default to left 196 197 tb.Label( 198 parent, 199 text=field["label"], 200 font=font 201 ).grid(row=row, column=0, columnspan=2, pady=(10, 5), sticky=align) 202 203 204 def _setupValidation(self, widget, validationName): 205 """Bind focus-out validation using a named validation function.""" 206 fn = self._validatorResolver(validationName) if validationName else None 207 if not callable(fn): 208 return 209 210 def validate(event): 211 if fn(widget.get()): 212 self.clearInvalid(widget) 213 else: 214 self.markInvalid(widget) 215 216 widget.bind("<FocusOut>", validate) 217 218 def markInvalid(self, widget): 219 """Mark a widget as invalid with red border styling.""" 220 widget.config(bootstyle="danger") 221 self.invalidEntries.add(widget) 222 223 def clearInvalid(self, widget): 224 """Clear error styling from a widget.""" 225 widget.config(bootstyle="none") 226 self.invalidEntries.discard(widget) 227 228 def addNextButton(self, parent, row, command, label="Nästa", tooltip=None, column=None, columnspan=None): 229 """ 230 Add a 'Next' button to the given row. Automatically spans the full width 231 of horizontal blocks if no column or columnspan is provided. 232 Blocks execution if any fields are invalid. 233 """
234 def wrapped(): 235 if self.invalidEntries: 236 msg.showerror("Fel", "Alla fält måste vara giltiga innan du kan fortsätta.") 237 return 238 command() 239 240 # Calculate default columnspan if not explicitly provided 241 if column is None or columnspan is None: 242 # Find all unique columns used in row=0 (block layout) 243 used_columns = set() 244 for child in parent.winfo_children(): 245 grid = child.grid_info() 246 if grid.get("row") == 0 and "column" in grid: 247 used_columns.add(int(grid["column"])) 248 249 if not used_columns: 250 column = 0 251 columnspan = 1 252 else: 253 column = 0 254 columnspan = max(used_columns) + 1 255 256 btn = tb.Button(parent, text=label, command=wrapped, bootstyle="success") 257 btn.grid(row=row, column=column, columnspan=columnspan, pady=(10, 5)) 258 259 if tooltip: 260 self.addTooltip(btn, tooltip) 261 262 return btn 263 264 265 def getCurrentFont(self): 266 """Return the default font currently used by ttkbootstrap widgets.""" 267 style = tb.Style() 268 fontName = style.lookup("TLabel", "font") 269 return tkFont.Font(font=fontName) 270 271 def addTooltip(self, widget, text): 272 """Attach a styled tooltip to a widget.""" 273 ToolTip(widget, text, bootstyle=(SECONDARY, INVERSE)) 274 275 def removeBlock(self, blockList, wrapper): 276 """ 277 Remove a UI block from view and list. Automatically reindexes 278 and repositions remaining blocks. 279 """
280 frame = wrapper.meta.get("frame") 281 layout = wrapper.meta.get("layout", "horizontal") 282 if frame: 283 frame.destroy() 284 blockList.remove(wrapper) 285 286 for i, block in enumerate(blockList): 287 frame = block.meta["frame"] 288 new_label = f"Butik {i + 1}" 289 290 # Update visual label 291 frame.config(text=new_label) 292 293 # Update meta so future logic knows the new label/index 294 block.meta["custom_label"] = new_label 295 block.meta["store_id"] = i + 1 # optional if store_id should follow display order 296 297 # Reposition depending on layout 298 if layout == "vertical": 299 frame.grid_configure(row=i, column=0) 300 else: 301 frame.grid_configure(row=0, column=i) 302 303 304 def addLabeledEntry(self, parent, row, label, variable, state="normal", tooltip=None, validation=None, column=0, vertical=False, columnspan=1): 305 """ 306 Add a labeled entry input field. Supports vertical and horizontal layouts, 307 optional tooltips and validation. 308 """
309 if vertical: 310 tb.Label(parent, text=label).grid(row=row, column=column, columnspan=columnspan, padx=5, pady=(5, 5)) 311 entry = tb.Entry(parent, textvariable=variable, state=state, width=self.widthEntry) 312 entry.grid(row=row + 1, column=column, padx=5, columnspan=columnspan, pady=(0, 10), sticky="ew") 313 else: 314 tb.Label(parent, text=label).grid(row=row, column=column, padx=5, pady=5, sticky="e") 315 entry = tb.Entry(parent, textvariable=variable, state=state, width=self.widthEntry) 316 entry.grid(row=row, column=column + 1, columnspan=columnspan, padx=5, pady=5, sticky="ew") 317 318 if tooltip: 319 self.addTooltip(entry, tooltip) 320 321 validationFunc = self.resolveValidator(validation) if validation else None 322 if callable(validationFunc): 323 def onFocusOut(event): 324 value = entry.get() 325 if validationFunc(value): 326 self.clearInvalid(entry) 327 else: 328 self.markInvalid(entry) 329 entry.bind("<FocusOut>", onFocusOut) 330 331 return entry 332 333 def addLabeledCombobox(self, parent, row, label, variable, values, tooltip=None, state="readonly", column=0, vertical=False, columnspan=1): 334 """Add a labeled combobox dropdown field with options.""" 335 if vertical: 336 tb.Label(parent, text=label).grid(row=row, column=column, columnspan=columnspan, padx=5, pady=(5, 0)) 337 combo = tb.Combobox(parent, textvariable=variable, values=values, state=state, width=self.widthCombo) 338 combo.grid(row=row + 1, column=column, columnspan=columnspan, padx=5, pady=(0, 10)) 339 else: 340 tb.Label(parent, text=label).grid(row=row, column=column, padx=5, pady=5, sticky="e") 341 combo = tb.Combobox(parent, textvariable=variable, values=values, state=state, width=self.widthCombo) 342 combo.grid(row=row, column=column + 1, columnspan=columnspan, padx=5, pady=5, sticky="ew") 343 344 if tooltip: 345 self.addTooltip(combo, tooltip) 346 347 return combo 348 349 def getNextAvailableRow(self, frame: tk.Widget) -> int: 350 """ 351 Return the next available row in a container by checking occupied grid rows. 352 """
353 used_rows = [ 354 child.grid_info().get("row") 355 for child in frame.winfo_children() 356 if "row" in child.grid_info() 357 ] 358 return max(used_rows, default=-1) + 1

                        <div id="Mimir.__init__" class="classattr">
                                    <input id="Mimir.__init__-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="name">Mimir</span><span class="signature pdoc-code condensed">(<span class="param"><span class="n">app</span></span>)</span>

            <label class="view-source-button" for="Mimir.__init__-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.__init__"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.__init__-20"><a href="#Mimir.__init__-20"><span class="linenos">20</span></a>    <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>

21 """ 22 Initialize Mimir with reference to the root app window. 23 """ 24 self.app = app 25 self.currentView = None 26 self.invalidEntries = set() 27 self._validatorResolver = lambda name: None 28 self.fieldRenderers = { 29 "entry": self._renderEntryField, 30 "combobox": self._renderComboboxField, 31 "heading": self._renderHeading, 32 } 33 self._allowThemeToggle = False

        <div class="docstring"><p>Initialize Mimir with reference to the root app window.</p>
                        </div>
                        <div id="Mimir.widthEntry" class="classattr">
                            <div class="attr variable">
        <span class="name">widthEntry</span>        =

20

</div>
<a class="headerlink" href="#Mimir.widthEntry"></a>



                        </div>
                        <div id="Mimir.widthCombo" class="classattr">
                            <div class="attr variable">
        <span class="name">widthCombo</span>        =

18

</div>
<a class="headerlink" href="#Mimir.widthCombo"></a>



                        </div>
                        <div id="Mimir.app" class="classattr">
                            <div class="attr variable">
        <span class="name">app</span>

    
</div>
<a class="headerlink" href="#Mimir.app"></a>



                        </div>
                        <div id="Mimir.currentView" class="classattr">
                            <div class="attr variable">
        <span class="name">currentView</span>

    
</div>
<a class="headerlink" href="#Mimir.currentView"></a>



                        </div>
                        <div id="Mimir.invalidEntries" class="classattr">
                            <div class="attr variable">
        <span class="name">invalidEntries</span>

    
</div>
<a class="headerlink" href="#Mimir.invalidEntries"></a>



                        </div>
                        <div id="Mimir.fieldRenderers" class="classattr">
                            <div class="attr variable">
        <span class="name">fieldRenderers</span>

    
</div>
<a class="headerlink" href="#Mimir.fieldRenderers"></a>



                        </div>
                        <div id="Mimir.allowDarkMode" class="classattr">
                                    <input id="Mimir.allowDarkMode-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">allowDarkMode</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span>, </span><span class="param"><span class="n">enabled</span><span class="o">=</span><span class="kc">True</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.allowDarkMode-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.allowDarkMode"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.allowDarkMode-35"><a href="#Mimir.allowDarkMode-35"><span class="linenos">35</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">allowDarkMode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">enabled</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>

36 """Enable or disable theme toggle button in the top right.""" 37 self._allowThemeToggle = enabled

        <div class="docstring"><p>Enable or disable theme toggle button in the top right.</p>
                        </div>
                        <div id="Mimir.setValidatorResolver" class="classattr">
                                    <input id="Mimir.setValidatorResolver-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">setValidatorResolver</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span>, </span><span class="param"><span class="n">resolverFunc</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.setValidatorResolver-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.setValidatorResolver"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.setValidatorResolver-44"><a href="#Mimir.setValidatorResolver-44"><span class="linenos">44</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">setValidatorResolver</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">resolverFunc</span><span class="p">):</span>

45 """Register a function that returns validation callbacks by name.""" 46 self._validatorResolver = resolverFunc

        <div class="docstring"><p>Register a function that returns validation callbacks by name.</p>
                        </div>
                        <div id="Mimir.switchView" class="classattr">
                                    <input id="Mimir.switchView-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">switchView</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span>, </span><span class="param"><span class="n">newFrameFunc</span>, </span><span class="param"><span class="n">gridOptions</span><span class="o">=</span><span class="kc">None</span>, </span><span class="param"><span class="o">**</span><span class="n">kwargs</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.switchView-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.switchView"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.switchView-48"><a href="#Mimir.switchView-48"><span class="linenos">48</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">switchView</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">newFrameFunc</span><span class="p">,</span> <span class="n">gridOptions</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>

49 """ 50 Replace the current view with a new frame built by newFrameFunc. 51 Automatically configures geometry and grid placement. 52 """
53 if self.currentView: 54 self.currentView.destroy() 55 56 container = tb.Frame(self.app) 57 container.grid(row=0, column=0, sticky="nsew") 58 container.columnconfigure(0, weight=1) 59 60 topBar = tb.Frame(container) 61 topBar.grid(row=0, column=0, sticky="ew") 62 63 if self._allowThemeToggle: 64 tb.Button(topBar, text="Theme", command=self._toggleTheme).pack(side="right", padx=10, pady=10) 65 66 newFrame = newFrameFunc(self, kwargs) 67 options = gridOptions or {"row": 1, "column": 0, "sticky": "n", "padx": 50, "pady": 20} 68 newFrame.grid(options) 69 self.app.rowconfigure(options.get("row", 0), weight=1) 70 self.app.columnconfigure(options.get("column", 0), weight=1) 71 self.currentView = newFrame 72 self.app.update_idletasks() 73 self.app.geometry("") 74 return newFrame

        <div class="docstring"><p>Replace the current view with a new frame built by newFrameFunc.

Automatically configures geometry and grid placement.

                        </div>
                        <div id="Mimir.popupView" class="classattr">
                                    <input id="Mimir.popupView-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">popupView</span><span class="signature pdoc-code multiline">(<span class="param">	<span class="bp">self</span>,</span><span class="param">	<span class="n">viewFunc</span>,</span><span class="param">	<span class="n">title</span><span class="o">=</span><span class="s1">&#39;Popup&#39;</span>,</span><span class="param">	<span class="n">size</span><span class="o">=</span><span class="s1">&#39;fit&#39;</span>,</span><span class="param">	<span class="n">width</span><span class="o">=</span><span class="mi">500</span>,</span><span class="param">	<span class="n">height</span><span class="o">=</span><span class="mi">400</span>,</span><span class="param">	<span class="n">modal</span><span class="o">=</span><span class="kc">False</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.popupView-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.popupView"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.popupView-76"><a href="#Mimir.popupView-76"><span class="linenos"> 76</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">popupView</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">viewFunc</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s2">&quot;Popup&quot;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="s2">&quot;fit&quot;</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span> <span class="n">height</span><span class="o">=</span><span class="mi">400</span><span class="p">,</span> <span class="n">modal</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>

77 """ 78 Open a popup window and embed a view frame into it. 79 If modal=True, interaction with other windows is blocked. 80 """
81 popup = tk.Toplevel(self.app) 82 popup.title(title) 83 popup.transient(self.app) 84 popup.resizable(False, False) 85 86 if size == "fit": 87 popup.update_idletasks() 88 popup.geometry("") 89 else: 90 popup.geometry(f"{width}x{height}") 91 92 popup.grid_rowconfigure(0, weight=1) 93 popup.grid_columnconfigure(0, weight=1) 94 viewFrame = viewFunc(self, popup) 95 viewFrame.grid(row=0, column=0, sticky="nsew") 96 97 if modal: 98 popup.grab_set() 99 popup.focus_set() 100 101 return popup

        <div class="docstring"><p>Open a popup window and embed a view frame into it.

If modal=True, interaction with other windows is blocked.

                        </div>
                        <div id="Mimir.renderBlockUI" class="classattr">
                                    <input id="Mimir.renderBlockUI-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">renderBlockUI</span><span class="signature pdoc-code multiline">(<span class="param">	<span class="bp">self</span>,</span><span class="param">	<span class="n">container</span>,</span><span class="param">	<span class="n">fields</span>,</span><span class="param">	<span class="n">blockList</span>,</span><span class="param">	<span class="n">layout</span><span class="o">=</span><span class="s1">&#39;horizontal&#39;</span>,</span><span class="param">	<span class="n">meta</span><span class="o">=</span><span class="kc">None</span>,</span><span class="param">	<span class="n">label</span><span class="o">=</span><span class="kc">None</span>,</span><span class="param">	<span class="n">noRemove</span><span class="o">=</span><span class="kc">False</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.renderBlockUI-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.renderBlockUI"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.renderBlockUI-103"><a href="#Mimir.renderBlockUI-103"><span class="linenos">103</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">renderBlockUI</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">container</span><span class="p">,</span> <span class="n">fields</span><span class="p">,</span> <span class="n">blockList</span><span class="p">,</span> <span class="n">layout</span><span class="o">=</span><span class="s2">&quot;horizontal&quot;</span><span class="p">,</span> <span class="n">meta</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">noRemove</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>

104 """ 105 Render a labeled input block with specified fields and layout style. 106 Automatically attaches the block to blockList and handles UI labels and deletion logic. 107 """
108 index = len(blockList) + 1 109 labelText = label or f"Block {index}" 110 frame = tb.LabelFrame(container, text=labelText) 111 frame.configure(labelanchor="n") 112
113 if layout == "vertical": 114 frame.grid(row=index - 1, column=0, padx=10, pady=10, sticky="w") 115 else: 116 frame.grid(row=0, column=index - 1, padx=10, pady=10, sticky="n") 117 118 boldFont = self.getCurrentFont() 119 boldFont.configure(weight="bold") 120 121 if meta is None: 122 meta = {} 123 if "custom_label" not in meta and label: 124 meta["custom_label"] = label 125 126 blockMeta = { 127 "frame": frame, 128 "fields": fields, 129 "fields_by_key": {f["key"]: f["variable"] for f in fields if "key" in f}, 130 "layout": layout, 131 **meta 132 } 133 134 135 wrapper = DataBlockWrapper(blockMeta) 136 blockList.append(wrapper) 137 138 row = 0 139 for field in fields: 140 ftype = field.get("type", "entry") 141 renderer = self.fieldRenderers.get(ftype) 142 if renderer: 143 renderer(frame, row, field) 144 145 # Adjust row offset based on field type 146 if ftype == "fileupload": 147 row += 3 148 elif ftype == "heading": 149 row += 1 150 else: 151 row += 2 152 153 if not noRemove: 154 removeBtn = tb.Button( 155 frame, 156 text="🗑️ Ta bort", 157 bootstyle="danger-outline", 158 command=lambda w=wrapper: self.removeBlock(blockList, w) 159 ) 160 removeBtn.grid(row=row, column=0, columnspan=2, pady=(10, 5)) 161 162 return wrapper

        <div class="docstring"><p>Render a labeled input block with specified fields and layout style.

Automatically attaches the block to blockList and handles UI labels and deletion logic.

                        </div>
                        <div id="Mimir.registerFieldType" class="classattr">
                                    <input id="Mimir.registerFieldType-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">registerFieldType</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span>, </span><span class="param"><span class="n">name</span>, </span><span class="param"><span class="n">renderer</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.registerFieldType-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.registerFieldType"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.registerFieldType-165"><a href="#Mimir.registerFieldType-165"><span class="linenos">165</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">registerFieldType</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">renderer</span><span class="p">):</span>

166 """Register a custom field renderer by name."""
167 self.fieldRenderers[name] = renderer

        <div class="docstring"><p>Register a custom field renderer by name.</p>
                        </div>
                        <div id="Mimir.markInvalid" class="classattr">
                                    <input id="Mimir.markInvalid-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">markInvalid</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span>, </span><span class="param"><span class="n">widget</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.markInvalid-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.markInvalid"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.markInvalid-218"><a href="#Mimir.markInvalid-218"><span class="linenos">218</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">markInvalid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">widget</span><span class="p">):</span>

219 """Mark a widget as invalid with red border styling.""" 220 widget.config(bootstyle="danger") 221 self.invalidEntries.add(widget)

        <div class="docstring"><p>Mark a widget as invalid with red border styling.</p>
                        </div>
                        <div id="Mimir.clearInvalid" class="classattr">
                                    <input id="Mimir.clearInvalid-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">clearInvalid</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span>, </span><span class="param"><span class="n">widget</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.clearInvalid-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.clearInvalid"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.clearInvalid-223"><a href="#Mimir.clearInvalid-223"><span class="linenos">223</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">clearInvalid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">widget</span><span class="p">):</span>

224 """Clear error styling from a widget.""" 225 widget.config(bootstyle="none") 226 self.invalidEntries.discard(widget)

        <div class="docstring"><p>Clear error styling from a widget.</p>
                        </div>
                        <div id="Mimir.addNextButton" class="classattr">
                                    <input id="Mimir.addNextButton-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">addNextButton</span><span class="signature pdoc-code multiline">(<span class="param">	<span class="bp">self</span>,</span><span class="param">	<span class="n">parent</span>,</span><span class="param">	<span class="n">row</span>,</span><span class="param">	<span class="n">command</span>,</span><span class="param">	<span class="n">label</span><span class="o">=</span><span class="s1">&#39;Nästa&#39;</span>,</span><span class="param">	<span class="n">tooltip</span><span class="o">=</span><span class="kc">None</span>,</span><span class="param">	<span class="n">column</span><span class="o">=</span><span class="kc">None</span>,</span><span class="param">	<span class="n">columnspan</span><span class="o">=</span><span class="kc">None</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.addNextButton-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.addNextButton"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.addNextButton-228"><a href="#Mimir.addNextButton-228"><span class="linenos">228</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">addNextButton</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">parent</span><span class="p">,</span> <span class="n">row</span><span class="p">,</span> <span class="n">command</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s2">&quot;Nästa&quot;</span><span class="p">,</span> <span class="n">tooltip</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">column</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">columnspan</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>

229 """ 230 Add a 'Next' button to the given row. Automatically spans the full width 231 of horizontal blocks if no column or columnspan is provided. 232 Blocks execution if any fields are invalid. 233 """
234 def wrapped(): 235 if self.invalidEntries: 236 msg.showerror("Fel", "Alla fält måste vara giltiga innan du kan fortsätta.") 237 return 238 command() 239 240 # Calculate default columnspan if not explicitly provided 241 if column is None or columnspan is None: 242 # Find all unique columns used in row=0 (block layout) 243 used_columns = set() 244 for child in parent.winfo_children(): 245 grid = child.grid_info() 246 if grid.get("row") == 0 and "column" in grid: 247 used_columns.add(int(grid["column"])) 248 249 if not used_columns: 250 column = 0 251 columnspan = 1 252 else: 253 column = 0 254 columnspan = max(used_columns) + 1 255 256 btn = tb.Button(parent, text=label, command=wrapped, bootstyle="success") 257 btn.grid(row=row, column=column, columnspan=columnspan, pady=(10, 5)) 258 259 if tooltip: 260 self.addTooltip(btn, tooltip) 261 262 return btn

        <div class="docstring"><p>Add a 'Next' button to the given row. Automatically spans the full width

of horizontal blocks if no column or columnspan is provided. Blocks execution if any fields are invalid.

                        </div>
                        <div id="Mimir.getCurrentFont" class="classattr">
                                    <input id="Mimir.getCurrentFont-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">getCurrentFont</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.getCurrentFont-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.getCurrentFont"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.getCurrentFont-265"><a href="#Mimir.getCurrentFont-265"><span class="linenos">265</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">getCurrentFont</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>

266 """Return the default font currently used by ttkbootstrap widgets.""" 267 style = tb.Style() 268 fontName = style.lookup("TLabel", "font") 269 return tkFont.Font(font=fontName)

        <div class="docstring"><p>Return the default font currently used by ttkbootstrap widgets.</p>
                        </div>
                        <div id="Mimir.addTooltip" class="classattr">
                                    <input id="Mimir.addTooltip-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">addTooltip</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span>, </span><span class="param"><span class="n">widget</span>, </span><span class="param"><span class="n">text</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.addTooltip-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.addTooltip"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.addTooltip-271"><a href="#Mimir.addTooltip-271"><span class="linenos">271</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">addTooltip</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">widget</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>

272 """Attach a styled tooltip to a widget.""" 273 ToolTip(widget, text, bootstyle=(SECONDARY, INVERSE))

        <div class="docstring"><p>Attach a styled tooltip to a widget.</p>
                        </div>
                        <div id="Mimir.removeBlock" class="classattr">
                                    <input id="Mimir.removeBlock-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">removeBlock</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span>, </span><span class="param"><span class="n">blockList</span>, </span><span class="param"><span class="n">wrapper</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.removeBlock-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.removeBlock"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.removeBlock-275"><a href="#Mimir.removeBlock-275"><span class="linenos">275</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">removeBlock</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">blockList</span><span class="p">,</span> <span class="n">wrapper</span><span class="p">):</span>

276 """ 277 Remove a UI block from view and list. Automatically reindexes 278 and repositions remaining blocks. 279 """
280 frame = wrapper.meta.get("frame") 281 layout = wrapper.meta.get("layout", "horizontal") 282 if frame: 283 frame.destroy() 284 blockList.remove(wrapper) 285 286 for i, block in enumerate(blockList): 287 frame = block.meta["frame"] 288 new_label = f"Butik {i + 1}" 289 290 # Update visual label 291 frame.config(text=new_label) 292 293 # Update meta so future logic knows the new label/index 294 block.meta["custom_label"] = new_label 295 block.meta["store_id"] = i + 1 # optional if store_id should follow display order 296 297 # Reposition depending on layout 298 if layout == "vertical": 299 frame.grid_configure(row=i, column=0) 300 else: 301 frame.grid_configure(row=0, column=i)

        <div class="docstring"><p>Remove a UI block from view and list. Automatically reindexes

and repositions remaining blocks.

                        </div>
                        <div id="Mimir.addLabeledEntry" class="classattr">
                                    <input id="Mimir.addLabeledEntry-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">addLabeledEntry</span><span class="signature pdoc-code multiline">(<span class="param">	<span class="bp">self</span>,</span><span class="param">	<span class="n">parent</span>,</span><span class="param">	<span class="n">row</span>,</span><span class="param">	<span class="n">label</span>,</span><span class="param">	<span class="n">variable</span>,</span><span class="param">	<span class="n">state</span><span class="o">=</span><span class="s1">&#39;normal&#39;</span>,</span><span class="param">	<span class="n">tooltip</span><span class="o">=</span><span class="kc">None</span>,</span><span class="param">	<span class="n">validation</span><span class="o">=</span><span class="kc">None</span>,</span><span class="param">	<span class="n">column</span><span class="o">=</span><span class="mi">0</span>,</span><span class="param">	<span class="n">vertical</span><span class="o">=</span><span class="kc">False</span>,</span><span class="param">	<span class="n">columnspan</span><span class="o">=</span><span class="mi">1</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.addLabeledEntry-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.addLabeledEntry"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.addLabeledEntry-304"><a href="#Mimir.addLabeledEntry-304"><span class="linenos">304</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">addLabeledEntry</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">parent</span><span class="p">,</span> <span class="n">row</span><span class="p">,</span> <span class="n">label</span><span class="p">,</span> <span class="n">variable</span><span class="p">,</span> <span class="n">state</span><span class="o">=</span><span class="s2">&quot;normal&quot;</span><span class="p">,</span> <span class="n">tooltip</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">validation</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">column</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">vertical</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">columnspan</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>

305 """ 306 Add a labeled entry input field. Supports vertical and horizontal layouts, 307 optional tooltips and validation. 308 """
309 if vertical: 310 tb.Label(parent, text=label).grid(row=row, column=column, columnspan=columnspan, padx=5, pady=(5, 5)) 311 entry = tb.Entry(parent, textvariable=variable, state=state, width=self.widthEntry) 312 entry.grid(row=row + 1, column=column, padx=5, columnspan=columnspan, pady=(0, 10), sticky="ew") 313 else: 314 tb.Label(parent, text=label).grid(row=row, column=column, padx=5, pady=5, sticky="e") 315 entry = tb.Entry(parent, textvariable=variable, state=state, width=self.widthEntry) 316 entry.grid(row=row, column=column + 1, columnspan=columnspan, padx=5, pady=5, sticky="ew") 317 318 if tooltip: 319 self.addTooltip(entry, tooltip) 320 321 validationFunc = self.resolveValidator(validation) if validation else None 322 if callable(validationFunc): 323 def onFocusOut(event): 324 value = entry.get() 325 if validationFunc(value): 326 self.clearInvalid(entry) 327 else: 328 self.markInvalid(entry) 329 entry.bind("<FocusOut>", onFocusOut) 330 331 return entry

        <div class="docstring"><p>Add a labeled entry input field. Supports vertical and horizontal layouts,

optional tooltips and validation.

                        </div>
                        <div id="Mimir.addLabeledCombobox" class="classattr">
                                    <input id="Mimir.addLabeledCombobox-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">addLabeledCombobox</span><span class="signature pdoc-code multiline">(<span class="param">	<span class="bp">self</span>,</span><span class="param">	<span class="n">parent</span>,</span><span class="param">	<span class="n">row</span>,</span><span class="param">	<span class="n">label</span>,</span><span class="param">	<span class="n">variable</span>,</span><span class="param">	<span class="n">values</span>,</span><span class="param">	<span class="n">tooltip</span><span class="o">=</span><span class="kc">None</span>,</span><span class="param">	<span class="n">state</span><span class="o">=</span><span class="s1">&#39;readonly&#39;</span>,</span><span class="param">	<span class="n">column</span><span class="o">=</span><span class="mi">0</span>,</span><span class="param">	<span class="n">vertical</span><span class="o">=</span><span class="kc">False</span>,</span><span class="param">	<span class="n">columnspan</span><span class="o">=</span><span class="mi">1</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="Mimir.addLabeledCombobox-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.addLabeledCombobox"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.addLabeledCombobox-333"><a href="#Mimir.addLabeledCombobox-333"><span class="linenos">333</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">addLabeledCombobox</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">parent</span><span class="p">,</span> <span class="n">row</span><span class="p">,</span> <span class="n">label</span><span class="p">,</span> <span class="n">variable</span><span class="p">,</span> <span class="n">values</span><span class="p">,</span> <span class="n">tooltip</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">state</span><span class="o">=</span><span class="s2">&quot;readonly&quot;</span><span class="p">,</span> <span class="n">column</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">vertical</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">columnspan</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>

334 """Add a labeled combobox dropdown field with options.""" 335 if vertical: 336 tb.Label(parent, text=label).grid(row=row, column=column, columnspan=columnspan, padx=5, pady=(5, 0)) 337 combo = tb.Combobox(parent, textvariable=variable, values=values, state=state, width=self.widthCombo) 338 combo.grid(row=row + 1, column=column, columnspan=columnspan, padx=5, pady=(0, 10)) 339 else: 340 tb.Label(parent, text=label).grid(row=row, column=column, padx=5, pady=5, sticky="e") 341 combo = tb.Combobox(parent, textvariable=variable, values=values, state=state, width=self.widthCombo) 342 combo.grid(row=row, column=column + 1, columnspan=columnspan, padx=5, pady=5, sticky="ew") 343 344 if tooltip: 345 self.addTooltip(combo, tooltip) 346 347 return combo

        <div class="docstring"><p>Add a labeled combobox dropdown field with options.</p>
                        </div>
                        <div id="Mimir.getNextAvailableRow" class="classattr">
                                    <input id="Mimir.getNextAvailableRow-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">getNextAvailableRow</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span>, </span><span class="param"><span class="n">frame</span><span class="p">:</span> <span class="n">tkinter</span><span class="o">.</span><span class="n">Widget</span></span><span class="return-annotation">) -> <span class="nb">int</span>:</span></span>

            <label class="view-source-button" for="Mimir.getNextAvailableRow-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#Mimir.getNextAvailableRow"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="Mimir.getNextAvailableRow-349"><a href="#Mimir.getNextAvailableRow-349"><span class="linenos">349</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">getNextAvailableRow</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">frame</span><span class="p">:</span> <span class="n">tk</span><span class="o">.</span><span class="n">Widget</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>

350 """ 351 Return the next available row in a container by checking occupied grid rows. 352 """
353 used_rows = [ 354 child.grid_info().get("row") 355 for child in frame.winfo_children() 356 if "row" in child.grid_info() 357 ] 358 return max(used_rows, default=-1) + 1

        <div class="docstring"><p>Return the next available row in a container by checking occupied grid rows.</p>
                        </div>
            </section>
            <section id="DataBlockWrapper">
                        <input id="DataBlockWrapper-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
<span class="def">class</span>
<span class="name">DataBlockWrapper</span>:

            <label class="view-source-button" for="DataBlockWrapper-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#DataBlockWrapper"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="DataBlockWrapper-6"><a href="#DataBlockWrapper-6"><span class="linenos"> 6</span></a><span class="k">class</span><span class="w"> </span><span class="nc">DataBlockWrapper</span><span class="p">:</span>

7 def init(self, meta): 8 self._meta = meta 9 10 @property 11 def meta(self): 12 return self._meta 13 14 def getattr(self, key): 15 fields = self._meta.get("fields_by_key", {}) 16 if key in fields: 17 return fields[key] 18 raise AttributeError(f"'{type(self).name}' object has no attribute '{key}'") 19 20 def get(self, key): 21 return self._meta.get("fields_by_key", {}).get(key) 22 23 def set(self, key, value): 24 try: 25 variable = self._meta["fields_by_key"][key] 26 if hasattr(variable, "set"): 27 variable.set(value) 28 else: 29 self._meta["fields_by_key"][key] = value 30 except KeyError: 31 raise KeyError(f"Key '{key}' not found in DataBlockWrapper.") 32 33 def items(self): 34 return self._meta.get("fields_by_key", {}).items() 35 36 def values(self): 37 return self._meta.get("fields_by_key", {}).values() 38 39 def keys(self): 40 return self._meta.get("fields_by_key", {}).keys() 41 42 def dir(self): 43 return super().dir() + list(self._meta.get("fields_by_key", {}).keys())

                        <div id="DataBlockWrapper.__init__" class="classattr">
                                    <input id="DataBlockWrapper.__init__-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="name">DataBlockWrapper</span><span class="signature pdoc-code condensed">(<span class="param"><span class="n">meta</span></span>)</span>

            <label class="view-source-button" for="DataBlockWrapper.__init__-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#DataBlockWrapper.__init__"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="DataBlockWrapper.__init__-7"><a href="#DataBlockWrapper.__init__-7"><span class="linenos">7</span></a>    <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">meta</span><span class="p">):</span>

8 self._meta = meta

                        </div>
                        <div id="DataBlockWrapper.meta" class="classattr">
                                    <input id="DataBlockWrapper.meta-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
meta
            <label class="view-source-button" for="DataBlockWrapper.meta-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#DataBlockWrapper.meta"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="DataBlockWrapper.meta-10"><a href="#DataBlockWrapper.meta-10"><span class="linenos">10</span></a>    <span class="nd">@property</span>

11 def meta(self): 12 return self._meta

                        </div>
                        <div id="DataBlockWrapper.get" class="classattr">
                                    <input id="DataBlockWrapper.get-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">get</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span>, </span><span class="param"><span class="n">key</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="DataBlockWrapper.get-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#DataBlockWrapper.get"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="DataBlockWrapper.get-20"><a href="#DataBlockWrapper.get-20"><span class="linenos">20</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>

21 return self._meta.get("fields_by_key", {}).get(key)

                        </div>
                        <div id="DataBlockWrapper.set" class="classattr">
                                    <input id="DataBlockWrapper.set-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">set</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span>, </span><span class="param"><span class="n">key</span>, </span><span class="param"><span class="n">value</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="DataBlockWrapper.set-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#DataBlockWrapper.set"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="DataBlockWrapper.set-23"><a href="#DataBlockWrapper.set-23"><span class="linenos">23</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">set</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>

24 try: 25 variable = self._meta["fields_by_key"][key] 26 if hasattr(variable, "set"): 27 variable.set(value) 28 else: 29 self._meta["fields_by_key"][key] = value 30 except KeyError: 31 raise KeyError(f"Key '{key}' not found in DataBlockWrapper.")

                        </div>
                        <div id="DataBlockWrapper.items" class="classattr">
                                    <input id="DataBlockWrapper.items-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">items</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="DataBlockWrapper.items-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#DataBlockWrapper.items"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="DataBlockWrapper.items-33"><a href="#DataBlockWrapper.items-33"><span class="linenos">33</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">items</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>

34 return self._meta.get("fields_by_key", {}).items()

                        </div>
                        <div id="DataBlockWrapper.values" class="classattr">
                                    <input id="DataBlockWrapper.values-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">values</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="DataBlockWrapper.values-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#DataBlockWrapper.values"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="DataBlockWrapper.values-36"><a href="#DataBlockWrapper.values-36"><span class="linenos">36</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">values</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>

37 return self._meta.get("fields_by_key", {}).values()

                        </div>
                        <div id="DataBlockWrapper.keys" class="classattr">
                                    <input id="DataBlockWrapper.keys-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
    <span class="def">def</span>
    <span class="name">keys</span><span class="signature pdoc-code condensed">(<span class="param"><span class="bp">self</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="DataBlockWrapper.keys-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#DataBlockWrapper.keys"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="DataBlockWrapper.keys-39"><a href="#DataBlockWrapper.keys-39"><span class="linenos">39</span></a>    <span class="k">def</span><span class="w"> </span><span class="nf">keys</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>

40 return self._meta.get("fields_by_key", {}).keys()

                        </div>
            </section>
            <section id="UtilityModule">
                        <input id="UtilityModule-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
<span class="def">class</span>
<span class="name">UtilityModule</span>:

            <label class="view-source-button" for="UtilityModule-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#UtilityModule"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="UtilityModule-6"><a href="#UtilityModule-6"><span class="linenos"> 6</span></a><span class="k">class</span><span class="w"> </span><span class="nc">UtilityModule</span><span class="p">:</span>

7 @staticmethod 8 def buildBlockMeta(fields, extra): 9 """ 10 Given a list of field definitions, return a structured metadata dictionary 11 ready to be passed to DataBlockWrapper. 12 13 Fields should be a list of dicts containing at least 'variable' and optionally 'key'. 14 Additional metadata (e.g. label, index) can be passed via kwargs. 15 """ 16 return { 17 extra, 18 "fields": fields, 19 "fields_by_key": {f["key"]: f["variable"] for f in fields if "key" in f}, 20 } 21 22 @staticmethod 23 def getBlockValues(wrapper, raw=False): 24 """ 25 Extract a dictionary of current values from a DataBlockWrapper. 26 27 If raw=True, return variable objects. Otherwise, return .get() results. 28 """ 29 output = {} 30 for key, var in wrapper.items(): 31 if hasattr(var, "get") and not raw: 32 output[key] = var.get() 33 else: 34 output[key] = var 35 return output 36 37 @staticmethod 38 def updateBlockFromDict(wrapper, data): 39 """ 40 Given a dict of values, update the DataBlockWrapper by setting matching keys. 41 """ 42 for key, value in data.items(): 43 try: 44 wrapper.set(key, value) 45 except KeyError: 46 continue 47 48 @staticmethod 49 def isBlockValid(wrapper, validatorResolver): 50 """ 51 Check if all fields in a block pass their validator. 52 The validatorResolver must take a string name and return a function. 53 """ 54 for f in wrapper.meta.get("fields", []): 55 name = f.get("validation") 56 var = f.get("variable") 57 if name and var and callable(validatorResolver(name)): 58 valid = validatorResolver(name)(var.get()) 59 if not valid: 60 return False 61 return True

                        <div id="UtilityModule.buildBlockMeta" class="classattr">
                                    <input id="UtilityModule.buildBlockMeta-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
@staticmethod
    <span class="def">def</span>
    <span class="name">buildBlockMeta</span><span class="signature pdoc-code condensed">(<span class="param"><span class="n">fields</span>, </span><span class="param"><span class="o">**</span><span class="n">extra</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="UtilityModule.buildBlockMeta-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#UtilityModule.buildBlockMeta"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="UtilityModule.buildBlockMeta-7"><a href="#UtilityModule.buildBlockMeta-7"><span class="linenos"> 7</span></a>    <span class="nd">@staticmethod</span>

8 def buildBlockMeta(fields, extra): 9 """ 10 Given a list of field definitions, return a structured metadata dictionary 11 ready to be passed to DataBlockWrapper. 12 13 Fields should be a list of dicts containing at least 'variable' and optionally 'key'. 14 Additional metadata (e.g. label, index) can be passed via kwargs. 15 """ 16 return { 17 extra, 18 "fields": fields, 19 "fields_by_key": {f["key"]: f["variable"] for f in fields if "key" in f}, 20 }

        <div class="docstring"><p>Given a list of field definitions, return a structured metadata dictionary

ready to be passed to DataBlockWrapper.

Fields should be a list of dicts containing at least 'variable' and optionally 'key'. Additional metadata (e.g. label, index) can be passed via kwargs.

                        </div>
                        <div id="UtilityModule.getBlockValues" class="classattr">
                                    <input id="UtilityModule.getBlockValues-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
@staticmethod
    <span class="def">def</span>
    <span class="name">getBlockValues</span><span class="signature pdoc-code condensed">(<span class="param"><span class="n">wrapper</span>, </span><span class="param"><span class="n">raw</span><span class="o">=</span><span class="kc">False</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="UtilityModule.getBlockValues-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#UtilityModule.getBlockValues"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="UtilityModule.getBlockValues-22"><a href="#UtilityModule.getBlockValues-22"><span class="linenos">22</span></a>    <span class="nd">@staticmethod</span>

23 def getBlockValues(wrapper, raw=False): 24 """ 25 Extract a dictionary of current values from a DataBlockWrapper. 26 27 If raw=True, return variable objects. Otherwise, return .get() results. 28 """ 29 output = {} 30 for key, var in wrapper.items(): 31 if hasattr(var, "get") and not raw: 32 output[key] = var.get() 33 else: 34 output[key] = var 35 return output

        <div class="docstring"><p>Extract a dictionary of current values from a DataBlockWrapper.</p>

If raw=True, return variable objects. Otherwise, return .get() results.

                        </div>
                        <div id="UtilityModule.updateBlockFromDict" class="classattr">
                                    <input id="UtilityModule.updateBlockFromDict-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
@staticmethod
    <span class="def">def</span>
    <span class="name">updateBlockFromDict</span><span class="signature pdoc-code condensed">(<span class="param"><span class="n">wrapper</span>, </span><span class="param"><span class="n">data</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="UtilityModule.updateBlockFromDict-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#UtilityModule.updateBlockFromDict"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="UtilityModule.updateBlockFromDict-37"><a href="#UtilityModule.updateBlockFromDict-37"><span class="linenos">37</span></a>    <span class="nd">@staticmethod</span>

38 def updateBlockFromDict(wrapper, data): 39 """ 40 Given a dict of values, update the DataBlockWrapper by setting matching keys. 41 """ 42 for key, value in data.items(): 43 try: 44 wrapper.set(key, value) 45 except KeyError: 46 continue

        <div class="docstring"><p>Given a dict of values, update the DataBlockWrapper by setting matching keys.</p>
                        </div>
                        <div id="UtilityModule.isBlockValid" class="classattr">
                                    <input id="UtilityModule.isBlockValid-view-source" class="view-source-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
@staticmethod
    <span class="def">def</span>
    <span class="name">isBlockValid</span><span class="signature pdoc-code condensed">(<span class="param"><span class="n">wrapper</span>, </span><span class="param"><span class="n">validatorResolver</span></span><span class="return-annotation">):</span></span>

            <label class="view-source-button" for="UtilityModule.isBlockValid-view-source"><span>View Source</span></label>

</div>
<a class="headerlink" href="#UtilityModule.isBlockValid"></a>
        <div class="pdoc-code codehilite"><pre><span></span><span id="UtilityModule.isBlockValid-48"><a href="#UtilityModule.isBlockValid-48"><span class="linenos">48</span></a>    <span class="nd">@staticmethod</span>

49 def isBlockValid(wrapper, validatorResolver): 50 """ 51 Check if all fields in a block pass their validator. 52 The validatorResolver must take a string name and return a function. 53 """ 54 for f in wrapper.meta.get("fields", []): 55 name = f.get("validation") 56 var = f.get("variable") 57 if name and var and callable(validatorResolver(name)): 58 valid = validatorResolver(name)(var.get()) 59 if not valid: 60 return False 61 return True

        <div class="docstring"><p>Check if all fields in a block pass their validator.

The validatorResolver must take a string name and return a function.

                        </div>
            </section>
</main>
<script> function escapeHTML(html) { return document.createElement('div').appendChild(document.createTextNode(html)).parentNode.innerHTML; } const originalContent = document.querySelector("main.pdoc"); let currentContent = originalContent; function setContent(innerHTML) { let elem; if (innerHTML) { elem = document.createElement("main"); elem.classList.add("pdoc"); elem.innerHTML = innerHTML; } else { elem = originalContent; } if (currentContent !== elem) { currentContent.replaceWith(elem); currentContent = elem; } } function getSearchTerm() { return (new URL(window.location)).searchParams.get("search"); } const searchBox = document.querySelector(".pdoc input[type=search]"); searchBox.addEventListener("input", function () { let url = new URL(window.location); if (searchBox.value.trim()) { url.hash = ""; url.searchParams.set("search", searchBox.value); } else { url.searchParams.delete("search"); } history.replaceState("", "", url.toString()); onInput(); }); window.addEventListener("popstate", onInput); let search, searchErr; async function initialize() { try { search = await new Promise((resolve, reject) => { const script = document.createElement("script"); script.type = "text/javascript"; script.async = true; script.onload = () => resolve(window.pdocSearch); script.onerror = (e) => reject(e); script.src = "search.js"; document.getElementsByTagName("head")[0].appendChild(script); }); } catch (e) { console.error("Cannot fetch pdoc search index"); searchErr = "Cannot fetch search index."; } onInput(); document.querySelector("nav.pdoc").addEventListener("click", e => { if (e.target.hash) { searchBox.value = ""; searchBox.dispatchEvent(new Event("input")); } }); } function onInput() { setContent((() => { const term = getSearchTerm(); if (!term) { return null } if (searchErr) { return `

Error: ${searchErr}

` } if (!search) { return "

Searching...

" } window.scrollTo({top: 0, left: 0, behavior: 'auto'}); const results = search(term); let html; if (results.length === 0) { html = `No search results for '${escapeHTML(term)}'.` } else { html = `

${results.length} search result${results.length > 1 ? "s" : ""} for '${escapeHTML(term)}'.

`; } for (let result of results.slice(0, 10)) { let doc = result.doc; let url = `${doc.modulename.replaceAll(".", "/")}.html`; if (doc.qualname) { url += `#${doc.qualname}`; } let heading; switch (result.doc.kind) { case "function": if (doc.fullname.endsWith(".__init__")) { heading = `${doc.fullname.replace(/\.__init__$/, "")}${doc.signature}`; } else { heading = `${doc.funcdef} ${doc.fullname}${doc.signature}`; } break; case "class": heading = `class ${doc.fullname}`; if (doc.bases) heading += `(${doc.bases})`; heading += `:`; break; case "variable": heading = `${doc.fullname}`; if (doc.annotation) heading += `${doc.annotation}`; if (doc.default_value) heading += ` = ${doc.default_value}`; break; default: heading = `${doc.fullname}`; break; } html += `
${heading}
${doc.doc}
`; } return html; })()); } if (getSearchTerm()) { initialize(); searchBox.value = getSearchTerm(); onInput(); } else { searchBox.addEventListener("focus", initialize, {once: true}); } searchBox.addEventListener("keydown", e => { if (["ArrowDown", "ArrowUp", "Enter"].includes(e.key)) { let focused = currentContent.querySelector(".search-result.focused"); if (!focused) { currentContent.querySelector(".search-result").classList.add("focused"); } else if ( e.key === "ArrowDown" && focused.nextElementSibling && focused.nextElementSibling.classList.contains("search-result") ) { focused.classList.remove("focused"); focused.nextElementSibling.classList.add("focused"); focused.nextElementSibling.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" }); } else if ( e.key === "ArrowUp" && focused.previousElementSibling && focused.previousElementSibling.classList.contains("search-result") ) { focused.classList.remove("focused"); focused.previousElementSibling.classList.add("focused"); focused.previousElementSibling.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" }); } else if ( e.key === "Enter" ) { focused.querySelector("a").click(); } } }); </script>

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jabs_mimir-0.3.14.tar.gz (109.3 kB view details)

Uploaded Source

Built Distribution

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

jabs_mimir-0.3.14-py3-none-any.whl (43.6 kB view details)

Uploaded Python 3

File details

Details for the file jabs_mimir-0.3.14.tar.gz.

File metadata

  • Download URL: jabs_mimir-0.3.14.tar.gz
  • Upload date:
  • Size: 109.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.9

File hashes

Hashes for jabs_mimir-0.3.14.tar.gz
Algorithm Hash digest
SHA256 a5749b77499a733079b623edc1c2489865d68b2d55d3198ad2948f5098cfcc44
MD5 b2510d0cb33de0634b05e6edb1c232d8
BLAKE2b-256 822117dd4113cee653eb21724a18b075f855e372725491bc89469729565b04cb

See more details on using hashes here.

File details

Details for the file jabs_mimir-0.3.14-py3-none-any.whl.

File metadata

  • Download URL: jabs_mimir-0.3.14-py3-none-any.whl
  • Upload date:
  • Size: 43.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.9

File hashes

Hashes for jabs_mimir-0.3.14-py3-none-any.whl
Algorithm Hash digest
SHA256 5bfe429de5637089fb7818fb4f5978c9f342d7ad1a3575a7b29c70aec642be04
MD5 1f061303cc7f8d670b06d80629a554a2
BLAKE2b-256 c0947a4bb7cf701caba91a8812ac8fe39b284328aff86baf62d3493e183b46cf

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