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:
- Read the current token
- If it is a COMPONENT, SCREEN, or IDENTIFIER, create a UINode
- Collect all following ATTRIBUTE tokens as the node's attributes
- Skip the NEWLINE
- If the next token is INDENT, recurse into
_parse_children _parse_childrenruns 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
- Add the component name to
COMPONENTSinlexer.py - Add a
_render_componentnamemethod toRendererinrenderer.py - Add CSS for
.pc-componentnamein_base_css - Add any JS the component needs in
_html_shell - Document in
COMPONENT_DOCSincli.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.apiis 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:
- Opens the
.pycromefile - Runs Lexer, Parser, Renderer
- Extracts window title and dimensions from the root node attrs
- Creates a pywebview window with the generated HTML
- 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
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
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 pycrome-1.4.0.tar.gz.
File metadata
- Download URL: pycrome-1.4.0.tar.gz
- Upload date:
- Size: 73.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b00326fd5a8077f433204faf17fd226b7fb5e96b90449e85b0539c1063bb6097
|
|
| MD5 |
27081df8dbba395fd1ed886d5ccd307a
|
|
| BLAKE2b-256 |
9e7ae4dec653088bcd24c0585e9b25fc847d04aef4ec4c00b1d1990b41e59a51
|
File details
Details for the file pycrome-1.4.0-py3-none-any.whl.
File metadata
- Download URL: pycrome-1.4.0-py3-none-any.whl
- Upload date:
- Size: 55.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ccab0b8bec1b7026bf4ab670001b8ae5ff984d075ff07902b251f1ef00988ef6
|
|
| MD5 |
ab89822bb2004bd65f3f5a58d11f88c1
|
|
| BLAKE2b-256 |
61ae390cf4e0455df4f4845bc891e8270626adc353fd15eddc15d862d3c8d7df
|