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">'Popup'</span>,</span><span class="param"> <span class="n">size</span><span class="o">=</span><span class="s1">'fit'</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">"Popup"</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="s2">"fit"</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">'horizontal'</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">"horizontal"</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">'Nästa'</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">"Nästa"</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">'normal'</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">"normal"</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">'readonly'</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">"readonly"</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">-></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">
<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>
</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">
<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">
<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">
<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">
<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 += `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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jabs_mimir-0.3.13.tar.gz.
File metadata
- Download URL: jabs_mimir-0.3.13.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e72b7a64533cd794b665a2eb3a35614896a47b3eff340bb9bc0441773b0b5dc
|
|
| MD5 |
a0b514fcecdbfb5b0c13b1bafb234d38
|
|
| BLAKE2b-256 |
6d2a210fb97b56c8c6c0d83aa867e74b683b708773788df8429d53ce32ecb7ae
|
File details
Details for the file jabs_mimir-0.3.13-py3-none-any.whl.
File metadata
- Download URL: jabs_mimir-0.3.13-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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04fbffab9f34d6894f107004e5401e9c964653af1fdeffe859dcebfeeccbbc9f
|
|
| MD5 |
1377f6fb89ea8e7b11396e19c061624d
|
|
| BLAKE2b-256 |
e8ed5ad564c08fd6c96398a5d7d9e1a062bed855472e3e957b57d4a4e8089874
|