Skip to main content

Python UI language for desktop applications

Project description

PyCrome

PyCrome is a Python-powered UI language for building modern desktop applications. Write declarative .pycrome files, connect a Python backend, and get a fully functional, beautifully styled desktop app — no HTML, CSS, or JavaScript required.

Built by Centra Holdings SMC Ltd, Uganda.


Why PyCrome

Most desktop UI frameworks require either verbose boilerplate (PySide6, Tkinter) or a full web stack (Electron). PyCrome gives you a clean declarative syntax, a modern Chromium-based renderer, and direct access to Python for all business logic.

Window title="My App" width=1200 height=800 theme="dark"
    Sidebar width=220
        NavItem label="Dashboard" icon="home" route="dashboard"
        NavItem label="Students" icon="users" route="students"
        ThemeSwitcher
    MainPanel
        Screen id="dashboard" default="true"
            Header title="Dashboard" subtitle="Welcome"
            Row
                StatCard label="Total Students" value="0" source="get_count()" icon="users" color="primary"
                StatCard label="Fees Collected" value="0" source="get_fees()" icon="fees" color="success"

Installation

pip install pywebview
cd F:/PROJECTS/PyCrome
pip install -e .

Quick Start

1. Create a new project

pycrome new myapp
cd myapp

2. Edit app.pycrome

Window title="My App" width=1200 height=800 theme="dark"
    Sidebar width=220
        NavItem label="Home" icon="home" route="home"
        ThemeSwitcher
    MainPanel
        Screen id="home" default="true"
            Header title="Hello PyCrome"
            Button label="Click Me" style="primary" onclick="say_hello()"

3. Edit backend.py

class Backend:
    def say_hello(self):
        return {"success": True, "message": "Hello from Python!"}

4. Run

pycrome run app.pycrome

Requirements

  • Python 3.12+
  • pywebview 6.x
  • Windows 10/11 (primary), macOS, Linux

Project Structure

myapp/
    app.pycrome           # UI definition
    backend.py            # Python business logic
    main.py               # Entry point
    pycrome.config.json   # Project configuration

CLI Commands

Command Description
pycrome run app.pycrome Launch the app
pycrome run app.pycrome --debug Launch with dev tools
pycrome new myapp Scaffold a new project
pycrome check app.pycrome Validate syntax
pycrome preview app.pycrome Preview in browser
pycrome watch app.pycrome Run with hot reload
pycrome build app.pycrome Package as .exe
pycrome list List all components
pycrome version Show version

Themes

PyCrome ships with five built-in themes: dark, light, blue, green, purple.

Set in your Window tag:

Window theme="dark"
Window theme="light"
Window theme="blue"

Or override individual colours:

Window theme="dark" primary="#e11d48"

Users can switch themes at runtime using the ThemeSwitcher component.


License

Proprietary. Built and maintained by Centra Holdings SMC Ltd.

======================================PyCrome Architecture =======================================================

This document describes how PyCrome works internally. It is intended for developers extending or maintaining the PyCrome engine.


Overview

PyCrome is a three-stage pipeline:

.pycrome source file
        |
   [1] Lexer          tokenises raw text into tokens
        |
   [2] Parser         builds a component tree (AST)
        |
   [3] Renderer       generates HTML, CSS, and JS
        |
   pywebview          displays as a native desktop window
        |
   Python Backend     handles all business logic

Project Structure

pycrome/
    __init__.py       package marker
    lexer.py          tokeniser
    parser.py         AST builder
    renderer.py       HTML/CSS/JS generator
    theme.py          theme definitions and utilities
    engine.py         orchestrator, pywebview launcher
    cli.py            command line interface

Stage 1: Lexer (lexer.py)

The lexer reads the raw .pycrome source text and converts it into a flat list of tokens.

Token Types

Token Description
COMPONENT A known component name (Window, Button, etc.)
SCREEN The Screen component specifically
IDENTIFIER An unrecognised name, treated as a component
ATTRIBUTE A key=value pair
INDENT Indentation increase
DEDENT Indentation decrease
NEWLINE End of line
EOF End of file

Indentation Tracking

The lexer maintains an indent stack. When indentation increases, an INDENT token is emitted. When it decreases, one or more DEDENT tokens are emitted to match.

Attribute Parsing

Attributes are parsed using a regex that handles three value types:

key="string value"    -> ATTRIBUTE (key, "string value")
key=123               -> ATTRIBUTE (key, 123)
key=true              -> ATTRIBUTE (key, True)

COMPONENTS Set

All valid component names are defined in the COMPONENTS set. Names not in this set are tokenised as IDENTIFIER. The parser treats IDENTIFIER tokens the same as COMPONENT tokens when building children, so custom or unknown components degrade gracefully.


Stage 2: Parser (parser.py)

The parser takes the flat token list and builds a tree of UINode objects.

UINode

@dataclass
class UINode:
    component: str                    # Component name
    attrs: dict[str, Any]             # Attribute key-value pairs
    children: list["UINode"]          # Child components

Parsing Algorithm

The parser uses a recursive descent approach:

  1. Read the current token
  2. If it is a COMPONENT, SCREEN, or IDENTIFIER, create a UINode
  3. Collect all following ATTRIBUTE tokens as the node's attributes
  4. Skip the NEWLINE
  5. If the next token is INDENT, recurse into _parse_children
  6. _parse_children runs until it hits a DEDENT or EOF

Tree Structure

A typical tree for a simple app:

Window (title="My App")
├── Sidebar (width=220)
│   ├── NavItem (label="Dashboard", route="dashboard")
│   └── ThemeSwitcher
├── MainPanel
│   └── Screen (id="dashboard", default=True)
│       ├── Header (title="Dashboard")
│       └── Button (label="Add", onclick="add()")
└── Modal (id="add-form", title="Add Record")
    └── Form (onsubmit="save")
        ├── FormField (id="name", label="Name")
        └── FormActions
            └── Button (label="Save", type="submit")

Stage 3: Renderer (renderer.py)

The renderer walks the UINode tree and generates a complete HTML page string.

Dispatch Pattern

For each node, the renderer calls a method named _render_{component_lower}:

def _render_node(self, node: UINode) -> str:
    method = f"_render_{node.component.lower()}"
    if hasattr(self, method):
        return getattr(self, method)(node)
    return self._render_generic(node)

Unknown components fall through to _render_generic, which wraps children in a <div class="pc-{component}">.

Adding a New Component

  1. Add the component name to COMPONENTS in lexer.py
  2. Add a _render_componentname method to Renderer in renderer.py
  3. Add CSS for .pc-componentname in _base_css
  4. Add any JS the component needs in _html_shell
  5. Document in COMPONENT_DOCS in cli.py

CSS Architecture

All CSS is generated in _base_css as a single f-string. CSS custom properties (variables) are generated from the active theme:

def generate_css_variables(theme: dict) -> str:
    vars_ = "\n".join(
        f"    --{key.replace('_', '-')}: {value};"
        for key, value in theme.items()
    )
    return f":root {{\n{vars_}\n}}"

Every component uses these variables rather than hard-coded colours, enabling runtime theme switching by simply updating the :root variables via JavaScript.

JavaScript Architecture

All JavaScript lives in _html_shell inside a single <script> block. Key subsystems:

System Functions
Theme pycromeSetTheme, PYCROME_THEMES
Navigation navigateTo, pycromeNavigate, navigate
Bridge pycromeCall
Modals pycromeOpenModal, pycromeCloseModal, pycromePopulateModal
Toasts pycromeToast, toast.success, toast.error
Forms pycromeHandleSubmit, pycromeValidateForm
Tables pycromeFilterTable, pycromeHandleRowClick
Pagination pycromeInitPagination, pycromeRenderPage, pycromeGotoPage
Context menu pycromeShowContext, pycromeCloseContext
Dropdown pycromeToggleDropdown, pycromeCloseDropdown
Confirmation pycromeConfirm, pycromeConfirmCancel
Charts pycromeRenderChart
DataGrid pycromeGridEdit, pycromeGridSave
Keyboard document.addEventListener('keydown', ...)

Python-JavaScript Bridge

pywebview exposes the backend instance as window.pywebview.api. Every public method on the backend class becomes callable from JavaScript.

The pycromeCall function handles:

  • Stripping parentheses from function name strings
  • Checking window.pywebview.api is ready
  • Returning parsed JSON if result is a string
  • Showing a toast if the function is not found
  • Catching and reporting exceptions
async function pycromeCall(func, ...args) {
    const fn = func.replace(/[(][^)]*[)]/, '').trim();
    if (window.pywebview && window.pywebview.api) {
        if (typeof window.pywebview.api[fn] === 'function') {
            const result = await window.pywebview.api[fn](...args);
            return typeof result === 'string' ? JSON.parse(result) : result;
        }
    }
    return null;
}

Safe Backend Wrapper

engine.py wraps the backend in a SafeBackend proxy:

def _make_safe_backend(self):
    for name in dir(backend):
        if name.startswith("_"):
            continue
        method = getattr(backend, name)
        if callable(method):
            setattr(safe, name, make_wrapper(method))

Each wrapper catches exceptions and returns a JSON error response rather than crashing the app.


Theme System (theme.py)

THEMES Dictionary

All built-in themes are defined as nested dictionaries keyed by theme name. Each theme maps snake_case variable names to CSS colour values.

Runtime Switching

generate_runtime_theme_js serialises all themes to a JavaScript object embedded in the HTML:

const PYCROME_THEMES = {
    "dark": { "--bg-primary": "#0f0f1a", ... },
    "light": { "--bg-primary": "#f8fafc", ... },
    ...
};

pycromeSetTheme updates all CSS variables on :root at runtime:

function pycromeSetTheme(themeName) {
    Object.entries(PYCROME_THEMES[themeName]).forEach(([key, value]) => {
        document.documentElement.style.setProperty(key, value);
    });
}

Colour Overrides

apply_overrides merges Window attribute overrides into the selected theme before rendering:

COLOUR_OVERRIDES = {
    "primary":    "primary",
    "background": "bg_primary",
    "surface":    "bg_surface",
    ...
}

def apply_overrides(theme, attrs):
    for attr_key, theme_key in COLOUR_OVERRIDES.items():
        if attr_key in attrs:
            theme[theme_key] = attrs[attr_key]
    return theme

Engine (engine.py)

The engine orchestrates the full pipeline:

  1. Opens the .pycrome file
  2. Runs Lexer, Parser, Renderer
  3. Extracts window title and dimensions from the root node attrs
  4. Creates a pywebview window with the generated HTML
  5. Passes the safe backend proxy as js_api

Error handling: if any stage fails, a readable error HTML page is shown instead of crashing.


CLI (cli.py)

The CLI uses Python's argparse with subcommands. Each command is a standalone function prefixed with cmd_. The main() function wires commands to parsers and dispatches.

Commands load PyCrome by inserting the project root into sys.path and importing dynamically, so the CLI works from any directory.


Extending PyCrome

Adding a Component

Minimum steps to add a new component called InfoPanel:

lexer.py

COMPONENTS = {
    ...,
    "InfoPanel",
}

renderer.py

def _render_infopanel(self, node: UINode) -> str:
    title   = node.attrs.get("title", "")
    content = node.attrs.get("content", "")
    children_html = "".join(self._render_node(c) for c in node.children)
    return f"""
    <div class="pc-infopanel">
        <div class="pc-infopanel-title">{title}</div>
        <div class="pc-infopanel-body">{content}{children_html}</div>
    </div>"""

renderer.py _base_css

        .pc-infopanel {{
            background: var(--bg-surface);
            border-left: 4px solid var(--primary);
            border-radius: var(--radius);
            padding: 16px 20px;
            margin-bottom: 16px;
        }}
        .pc-infopanel-title {{
            font-weight: 600;
            color: var(--text-primary);
            margin-bottom: 8px;
        }}
        .pc-infopanel-body {{
            color: var(--text-secondary);
            font-size: 14px;
        }}

cli.py COMPONENT_DOCS

"InfoPanel": "Information panel with coloured border. Attrs: title, content",

Usage in .pycrome:

InfoPanel title="Notice" content="Term 2 fees are due by 15th March."

Performance Notes

  • The HTML generation happens once at startup. There is no virtual DOM or diff system.
  • All data loading is asynchronous via the Python bridge.
  • Table filtering is done client-side in JS for instant response.
  • Pagination loads all data once then slices client-side for small to medium datasets. For very large datasets, implement server-side pagination in the backend.
  • Column resizing state is not persisted between sessions.

=================================PyCrome Examples============================================

Real-world usage examples for common application patterns.


School Management System

A complete school management screen with students, fees, and dashboard.

app.pycrome

Window title="School Manager" width=1400 height=900 theme="dark"
    Sidebar width=240
        NavItem label="Dashboard" icon="home" route="dashboard"
        NavItem label="Students" icon="users" route="students"
        NavItem label="Fees" icon="fees" route="fees"
        NavItem label="Reports" icon="reports" route="reports"
        NavItem label="Settings" icon="settings" route="settings"
        ThemeSwitcher
    MainPanel
        Screen id="dashboard" default="true"
            Header title="Dashboard" subtitle="School overview"
            Row
                StatCard label="Total Students" value="0" icon="users" color="primary" source="get_student_count()"
                StatCard label="Fees Collected" value="0" icon="fees" color="success" source="get_fees_collected()"
                StatCard label="Outstanding" value="0" icon="warning" color="danger" source="get_outstanding()"
                StatCard label="Active" value="0" icon="check" color="success" source="get_active_count()"
            Spacer size=24
            Row
                Col span=2
                    Card
                        Label text="Fee Collection Progress"
                        ProgressBar label="Term 1" value="0" source="get_term1_progress()" color="success"
                        ProgressBar label="Term 2" value="0" source="get_term2_progress()" color="warning"
                        ProgressBar label="Term 3" value="0" source="get_term3_progress()" color="danger"
                Col span=1
                    Card
                        Label text="Quick Actions"
                        Button label="Add Student" style="primary" icon="add" onclick="pycromeOpenModal('add-student')"
                        Button label="Record Payment" style="primary" icon="fees" onclick="pycromeOpenModal('add-payment')"

        Screen id="students"
            Header title="Students" subtitle="Manage enrolled students"
            Toolbar
                ToolbarItem label="Add Student" icon="add" style="primary" onclick="pycromeOpenModal('add-student')"
                ToolbarItem label="Export CSV" icon="download" style="primary" onclick="export_students()"
                ToolbarItem separator=true
                ToolbarItem label="Refresh" icon="refresh" style="primary" onclick="refresh_students()"
            SearchBar id="student_search" placeholder="Search by name, class, phone..." target="students_table"
            Table source="get_students()" id="students_table" selectable="true" on_row_click="on_student_selected" paginate="true" page_size="25"
                Column field="id" label="ID"
                Column field="name" label="Full Name"
                Column field="class" label="Class"
                Column field="phone" label="Phone"
                Column field="fees_balance" label="Balance"
                Column field="status" label="Status"
            ContextMenu id="student_ctx" target="students_table"
                ContextMenuItem label="Edit Student" icon="edit" onclick="pycromeOpenModal('edit-student')"
                ContextMenuItem label="View Fees" icon="fees" onclick="navigate('fees')"
                ContextMenuItem separator=true
                ContextMenuItem label="Delete" icon="delete" style="danger" onclick="pycromeDeleteStudent()"

        Screen id="fees"
            Header title="Fee Management" subtitle="Track payments"
            Toolbar
                ToolbarItem label="Record Payment" icon="add" style="primary" onclick="pycromeOpenModal('add-payment')"
                ToolbarItem label="Export" icon="download" style="primary" onclick="export_fees()"
            SearchBar id="fees_search" placeholder="Search transactions..." target="fees_table"
            Table source="get_fee_transactions()" id="fees_table" selectable="true" paginate="true" page_size="20"
                Column field="id" label="Receipt"
                Column field="student_name" label="Student"
                Column field="amount" label="Amount (UGX)"
                Column field="payment_date" label="Date"
                Column field="payment_method" label="Method"
                Column field="status" label="Status"

    Modal id="add-student" title="Add New Student" width=600
        Form onsubmit="add_student"
            Row
                Col span=1
                    FormField id="student_name" label="Full Name" type="text" placeholder="Enter full name" required=true
                    FormField id="student_class" label="Class" type="text" placeholder="S1, S2, S3..." required=true
                    FormField id="student_phone" label="Phone Number" type="tel" placeholder="07XXXXXXXX"
                    FormField id="parent_name" label="Parent/Guardian" type="text" placeholder="Parent full name"
                Col span=1
                    FormField id="student_gender" label="Gender" type="text" placeholder="Male / Female"
                    FormField id="student_dob" label="Date of Birth" type="date"
                    FormField id="student_email" label="Email" type="email" placeholder="student@example.com"
                    FormField id="student_fees" label="Annual Fees (UGX)" type="number" value=500000
            FormActions
                Button label="Cancel" onclick="pycromeCloseModal('add-student')"
                Button label="Save Student" style="primary" type="submit"

    Modal id="add-payment" title="Record Fee Payment" width=500
        Form onsubmit="add_payment"
            FormField id="payment_student_id" label="Student ID" type="text" placeholder="STU001" required=true
            FormField id="payment_amount" label="Amount (UGX)" type="number" placeholder="0" required=true
            FormField id="payment_date" label="Payment Date" type="date" required=true
            FormField id="payment_method" label="Payment Method" type="text" placeholder="Cash / MTN / Airtel / Bank" required=true
            FormField id="payment_receipt" label="Receipt Number" type="text" placeholder="RCP001"
            FormActions
                Button label="Cancel" onclick="pycromeCloseModal('add-payment')"
                Button label="Record Payment" style="primary" type="submit"

Inventory System

Window title="Inventory" width=1200 height=800 theme="dark"
    Sidebar width=220
        NavItem label="Stock" icon="package" route="stock"
        NavItem label="Orders" icon="list" route="orders"
        ThemeSwitcher
    MainPanel
        Screen id="stock" default="true"
            Header title="Stock" subtitle="Current inventory"
            Row
                StatCard label="Total Items" value="0" source="get_item_count()" icon="package" color="primary"
                StatCard label="Low Stock" value="0" source="get_low_stock_count()" icon="warning" color="danger"
                StatCard label="Stock Value" value="0" source="get_stock_value()" icon="money" color="success"
            Spacer size=16
            Toolbar
                ToolbarItem label="Add Item" icon="add" style="primary" onclick="pycromeOpenModal('add-item')"
                ToolbarItem label="Export" icon="download" style="primary" onclick="export_stock()"
            SearchBar id="stock_search" placeholder="Search items..." target="stock_table"
            Table source="get_stock()" id="stock_table" selectable="true" paginate="true" page_size="20"
                Column field="sku" label="SKU"
                Column field="name" label="Item Name"
                Column field="quantity" label="Qty"
                Column field="unit_price" label="Unit Price"
                Column field="total_value" label="Total Value"
                Column field="status" label="Status"

Settings Screen

Screen id="settings"
    Header title="Settings" subtitle="Application configuration"
    Tabs id="settings_tabs"
        Tab id="general" label="General" icon="settings"
            Card
                Label text="School Information"
                FormField id="school_name" label="School Name" type="text" value="CBCentra Academy"
                FormField id="school_address" label="Address" type="text"
                FormField id="academic_year" label="Academic Year" type="text" value="2024-2025"
                FormField id="current_term" label="Current Term" type="text" value="Term 1"
                Button label="Save General Settings" style="primary" icon="save" onclick="save_general_settings()"
        Tab id="fees" label="Fee Settings" icon="fees"
            Card
                Label text="Fee Structure"
                FormField id="tuition_fee" label="Tuition Fee (UGX)" type="number" value=500000
                FormField id="late_fee" label="Late Payment Fee (UGX)" type="number" value=50000
                FormField id="exam_fee" label="Exam Fee (UGX)" type="number" value=75000
                Button label="Save Fee Settings" style="primary" icon="save" onclick="save_fee_settings()"
        Tab id="notifications" label="Notifications" icon="bell"
            Card
                Label text="Notification Preferences"
                Toggle id="email_notifications" label="Email Notifications" checked=true onchange="toggle_email_notif()"
                Toggle id="sms_notifications" label="SMS Notifications" checked=false onchange="toggle_sms_notif()"
                Toggle id="fee_reminders" label="Fee Payment Reminders" checked=true onchange="toggle_fee_reminders()"

Multi-step Form

Screen id="enrollment"
    Header title="New Enrollment" subtitle="Complete all steps"
    Stepper active=0
        StepperItem label="Personal Info"
        StepperItem label="Academic Info"
        StepperItem label="Fee Setup"
        StepperItem label="Complete"
    Spacer size=24
    Card
        Label text="Step 1: Personal Information"
        FormField id="enroll_name" label="Full Name" type="text" required=true
        FormField id="enroll_dob" label="Date of Birth" type="date" required=true
        FormField id="enroll_gender" label="Gender" type="text" required=true
        FormField id="enroll_address" label="Home Address" type="text"
        Row
            Button label="Cancel" onclick="navigate('students')"
            Button label="Next: Academic Info" style="primary" icon="arrow-right" onclick="next_enrollment_step()"

Dashboard with Charts

Screen id="analytics" default="true"
    Header title="Analytics" subtitle="School performance overview"
    Row
        StatCard label="Enrollment Rate" value="94%" icon="trending-up" color="success"
        StatCard label="Fee Collection" value="78%" icon="fees" color="warning"
        StatCard label="Attendance" value="89%" icon="check" color="primary"
    Spacer size=24
    Row
        Col span=2
            Chart title="Monthly Fee Collection (UGX)" type="bar" source="get_monthly_fees()" id="monthly_fees" height=280
        Col span=1
            Chart title="Student Distribution by Class" type="doughnut" source="get_class_distribution()" id="class_dist" height=280
    Spacer size=16
    Row
        Col span=1
            Card
                Label text="Fee Collection Progress by Term"
                ProgressBar label="Term 1" value="92" color="success"
                ProgressBar label="Term 2" value="78" color="warning"
                ProgressBar label="Term 3" value="45" color="danger"
        Col span=1
            Card
                Label text="Status Overview"
                Badge text="Active" style="success"
                Badge text="Pending Fees" style="warning"
                Badge text="Overdue" style="danger"
                Badge text="Graduated" style="muted"

Backend for the Examples Above

from datetime import datetime
import mysql.connector
from mysql.connector import pooling

class SchoolBackend:
    def __init__(self):
        self.pool = pooling.MySQLConnectionPool(
            pool_name="school_pool",
            pool_size=5,
            host="localhost",
            database="school_db",
            user="root",
            password="password"
        )
        self.current_student_id = None

    def _conn(self):
        return self.pool.get_connection()

    # Dashboard stats
    def get_student_count(self):
        conn = self._conn()
        cur = conn.cursor()
        cur.execute("SELECT COUNT(*) FROM students")
        count = cur.fetchone()[0]
        cur.close(); conn.close()
        return {"value": count}

    def get_fees_collected(self):
        conn = self._conn()
        cur = conn.cursor()
        cur.execute("SELECT SUM(amount) FROM fee_transactions WHERE status='Completed'")
        total = cur.fetchone()[0] or 0
        cur.close(); conn.close()
        return {"value": f"UGX {total:,.0f}"}

    def get_outstanding(self):
        conn = self._conn()
        cur = conn.cursor()
        cur.execute("SELECT SUM(fees_balance) FROM students WHERE fees_balance > 0")
        total = cur.fetchone()[0] or 0
        cur.close(); conn.close()
        return {"value": f"UGX {total:,.0f}"}

    def get_active_count(self):
        conn = self._conn()
        cur = conn.cursor()
        cur.execute("SELECT COUNT(*) FROM students WHERE status='Active'")
        count = cur.fetchone()[0]
        cur.close(); conn.close()
        return {"value": count}

    # Students
    def get_students(self):
        conn = self._conn()
        cur = conn.cursor(dictionary=True)
        cur.execute("SELECT * FROM students ORDER BY name")
        rows = cur.fetchall()
        cur.close(); conn.close()
        return rows

    def on_student_selected(self, row_data):
        self.current_student_id = row_data.get("id")
        return {"success": True, "data": row_data}

    def add_student(self, form_data):
        name = form_data.get("student_name", "").strip()
        if not name:
            return {"success": False, "message": "Student name is required."}
        conn = self._conn()
        cur = conn.cursor()
        try:
            cur.execute(
                "INSERT INTO students (name, class, phone, parent_name, fees_balance, status) VALUES (%s, %s, %s, %s, %s, 'Active')",
                (name, form_data.get("student_class"), form_data.get("student_phone"),
                 form_data.get("parent_name"), float(form_data.get("student_fees", 0)))
            )
            conn.commit()
            return {"success": True, "message": f"Student '{name}' enrolled.", "reload_table": "students_table"}
        except Exception as e:
            conn.rollback()
            return {"success": False, "message": str(e)}
        finally:
            cur.close(); conn.close()

    def delete_student(self, data=None):
        student_id = (data or {}).get("id") or self.current_student_id
        if not student_id:
            return {"success": False, "message": "No student selected."}
        conn = self._conn()
        cur = conn.cursor()
        try:
            cur.execute("DELETE FROM students WHERE id = %s", (student_id,))
            conn.commit()
            self.current_student_id = None
            return {"success": True, "message": "Student deleted.", "reload_table": "students_table"}
        except Exception as e:
            conn.rollback()
            return {"success": False, "message": str(e)}
        finally:
            cur.close(); conn.close()

    # Fees
    def get_fee_transactions(self):
        conn = self._conn()
        cur = conn.cursor(dictionary=True)
        cur.execute("SELECT * FROM fee_transactions ORDER BY payment_date DESC")
        rows = cur.fetchall()
        cur.close(); conn.close()
        return rows

    def add_payment(self, form_data):
        student_id = form_data.get("payment_student_id", "").strip()
        amount = float(form_data.get("payment_amount", 0))
        if not student_id:
            return {"success": False, "message": "Student ID is required."}
        if amount <= 0:
            return {"success": False, "message": "Amount must be greater than zero."}
        conn = self._conn()
        cur = conn.cursor()
        try:
            cur.execute(
                "INSERT INTO fee_transactions (student_id, amount, payment_date, payment_method, receipt_number, status) VALUES (%s, %s, %s, %s, %s, 'Completed')",
                (student_id, amount, form_data.get("payment_date"), form_data.get("payment_method"), form_data.get("payment_receipt"))
            )
            cur.execute("UPDATE students SET fees_paid = fees_paid + %s, fees_balance = GREATEST(0, fees_balance - %s) WHERE id = %s", (amount, amount, student_id))
            conn.commit()
            return {"success": True, "message": f"Payment of UGX {amount:,.0f} recorded.", "reload_table": "fees_table"}
        except Exception as e:
            conn.rollback()
            return {"success": False, "message": str(e)}
        finally:
            cur.close(); conn.close()

    # Charts
    def get_monthly_fees(self):
        return {
            "labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
            "datasets": [{
                "label": "Fees Collected (UGX)",
                "data": [4500000, 3800000, 5200000, 4100000, 6000000, 5500000],
                "backgroundColor": "rgba(79,70,229,0.5)",
                "borderColor": "rgba(79,70,229,1)"
            }]
        }

    def get_class_distribution(self):
        return {
            "labels": ["S1", "S2", "S3", "S4", "S5", "S6"],
            "datasets": [{
                "data": [45, 42, 38, 36, 30, 28],
                "backgroundColor": [
                    "#4f46e5", "#10b981", "#f59e0b",
                    "#ef4444", "#8b5cf6", "#06b6d4"
                ]
            }]
        }

===========================PyCrome Changelog==================================================


Version 1.0.0 — April 2026

Initial release. Built by Centra Holdings SMC Ltd, Uganda.

Core Engine

  • Lexer with indentation tracking and full attribute parsing
  • Recursive descent parser building UINode component trees
  • HTML/CSS/JS renderer with component dispatch pattern
  • pywebview integration for native desktop windows using Chromium
  • Python-JavaScript bridge via pywebview API
  • Safe backend wrapper catching all exceptions
  • Graceful error screen for parse and render failures
  • Five built-in themes: dark, light, blue, green, purple
  • Runtime theme switching with localStorage persistence
  • Custom colour overrides directly in Window attributes

Layout Components

  • Window, Sidebar, MainPanel, Screen
  • Row, Col, Card, Spacer, Divider

Navigation Components

  • NavItem, ThemeSwitcher, Navbar
  • Breadcrumb, BreadcrumbItem
  • Tabs, Tab

Data Display Components

  • Table with column resizing, row selection, sticky first column
  • Column, Pagination
  • DataGrid with inline cell editing
  • StatCard with backend data loading and trend display
  • Badge, ProgressBar
  • Chart powered by Chart.js (bar, line, pie, doughnut)

Input Components

  • Input, SearchBar with live table filtering
  • Select, Option, Textarea
  • DatePicker, FileUpload with drag and drop
  • Toggle

Action Components

  • Button, Toolbar, ToolbarItem
  • Dropdown, DropdownItem
  • ContextMenu, ContextMenuItem

Feedback Components

  • Modal with animation and form reset on close
  • Toast notifications (success, error, warning, info)
  • Confirmation dialog
  • Form validation with inline error messages
  • Loading states for buttons and containers
  • Tooltip, NotificationBell
  • Accordion, AccordionItem
  • Stepper, StepperItem
  • Spinner

Form Components

  • Form with submit handling and validation
  • FormField, FormActions
  • Select, Textarea

Developer Experience

  • CLI: run, new, check, preview, watch, build, init, list, version
  • Hot reload via watch command
  • Debug mode with browser dev tools
  • PyInstaller build integration
  • pycrome.config.json project configuration
  • Keyboard shortcuts: Ctrl+F, Ctrl+N, Escape, Delete

Roadmap

Version 1.1.0 — Planned

  • MySQL connection helper in the engine
  • Export to CSV built-in backend utility
  • Print support
  • Notification bell dropdown panel
  • Table column sorting
  • Form field types: checkbox, radio group
  • Sidebar brand area with logo support
  • Window icon support

Version 1.2.0 — Planned

  • Multi-window support
  • System tray integration
  • Auto-update mechanism
  • CBCentra School Management System as reference implementation
  • PyCrome package on PyPI

@"

PyCrome

PyPI version License: MIT

Python DSL UI Framework for Desktop Applications

Build beautiful, responsive desktop applications using a simple declarative language. No HTML/CSS/JavaScript knowledge required.

🚀 Quick Start

Installation

pip install pycrome

Project details


Download files

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

Source Distribution

pycrome-1.4.6.tar.gz (258.9 kB view details)

Uploaded Source

Built Distribution

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

pycrome-1.4.6-py3-none-any.whl (245.0 kB view details)

Uploaded Python 3

File details

Details for the file pycrome-1.4.6.tar.gz.

File metadata

  • Download URL: pycrome-1.4.6.tar.gz
  • Upload date:
  • Size: 258.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for pycrome-1.4.6.tar.gz
Algorithm Hash digest
SHA256 880b30c355822e4ac7c8a0abc7336b2bfe34ca1dbd4acfd50983afa00089fe6f
MD5 84b399fa3feaf372bfa5f9d015178e67
BLAKE2b-256 496387a57ea1ae940f44a3c06de8e6143aee0df6cddc37de791c58bb99014b1b

See more details on using hashes here.

File details

Details for the file pycrome-1.4.6-py3-none-any.whl.

File metadata

  • Download URL: pycrome-1.4.6-py3-none-any.whl
  • Upload date:
  • Size: 245.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for pycrome-1.4.6-py3-none-any.whl
Algorithm Hash digest
SHA256 73a9dd7c353647c52ae9ca55f807f7c48084eea890ea153fc51666ae4fdeee7b
MD5 d08e95a5e6787c9f1353b06bec59409b
BLAKE2b-256 62b7e5258e7a80b2de5a37e7ef931f1c3d578029c4241772fb7ad023d19d368c

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