Project Structure
bootstack is designed to be used like a framework, not a loose collection of widgets. A good project structure makes it easy to:
- grow from a prototype into a real application
- keep UI code maintainable (views, state, services)
- package reliably with PyInstaller (assets, icons, translations)
This guide shows a structure that works well for bootstack apps and avoids the most common packaging pitfalls.
A practical default layout
This is a solid “start here” structure for most apps:
my_app/
pyproject.toml
README.md
src/
my_app/
__init__.py
__main__.py
app.py # App entry + wiring
settings.py # AppSettings / configuration defaults
state.py # Signals and shared app state
views/
__init__.py
main_window.py # Top-level UI composition
widgets/ # Optional: custom composite widgets
__init__.py
services/ # Optional: IO, data, network, persistence
__init__.py
assets/
icons/ # App icon files + brand assets
images/ # App images
i18n/
en.po # Optional: source translations
en.mo # Optional: compiled translations
tests/
scripts/
build.py # Optional: helper scripts
build/ # PyInstaller work dir (ignored)
dist/ # PyInstaller output (ignored)
Why this works:
src/layout prevents accidental imports from your repo root.app.pyowns framework wiring (theme, settings, menu, app state).views/is where you compose screens from widgets and containers.assets/andi18n/are explicit so packaging can include them reliably.
See Guides → App Structure for how windows, layout, and state fit together.
Entry points
__main__.py
Use python -m my_app for development and to give PyInstaller a clear entry.
# src/my_app/__main__.py
from .app import main
if __name__ == "__main__":
main()
app.py
Keep main() small and explicit.
# src/my_app/app.py
import bootstack as bs
from .settings import settings
from .views.main_window import MainWindow
def main() -> None:
app = bs.App(title=settings.title, theme=settings.theme)
MainWindow(app).pack(fill="both", expand=True)
app.mainloop()
Where bootstack concepts live
Settings
Put framework-level defaults in one place.
- theme choice
- localization defaults
- behavior toggles (if you expose them)
# src/my_app/settings.py
from dataclasses import dataclass
@dataclass(frozen=True)
class Settings:
title: str = "My App"
theme: str = "cosmo"
settings = Settings()
See Guides → App Settings for using AppSettings and framework configuration.
State (signals)
Signals represent shared state and keep UI reactive without tangled callbacks.
# src/my_app/state.py
import bootstack as bs
status = bs.Signal("Ready")
See Guides → Reactivity for the full signals API.
Views (composition)
Views assemble widgets into real screens. Think “page” or “window content”.
# src/my_app/views/main_window.py
import bootstack as bs
class MainWindow(bs.Frame):
def __init__(self, master):
super().__init__(master, padding=20)
bs.Label(self, text="Welcome").pack(anchor="w")
bs.Button(self, text="Continue", accent="primary").pack(pady=(12, 0))
See Guides → Layout for recommended containers and structure.
Assets and packaging strategy (PyInstaller)
Packaging is where structure matters most.
Keep assets in your package
Put images/icons under src/my_app/assets/... so they can be included as package data.
At runtime, avoid hardcoding filesystem paths relative to the working directory.
Instead, resolve assets via a single helper (so dev + PyInstaller behave the same).
# src/my_app/resources.py
from __future__ import annotations
from pathlib import Path
def resource_path(*parts: str) -> Path:
# Works in editable installs and in PyInstaller onefile/onedir builds.
base = Path(__file__).resolve().parent
return base.joinpath(*parts)
Use it:
from .resources import resource_path
icon_file = resource_path("assets", "icons", "app.png")
Note
PyInstaller extraction paths differ between onefile and onedir builds.
Keeping all asset access behind resource_path() makes this painless later.
Prefer “onedir” during development
For early packaging work, --onedir is simpler to debug.
- easy to inspect bundled files
- quicker iteration
- fewer surprises
When stable, you can switch to --onefile.
A PyInstaller-friendly build checklist
When you plan to distribute, structure with these in mind:
- One entry point (
python -m my_app/my_app.__main__) - All assets stored under your package (
src/my_app/assets) - Localization files in a predictable location (
src/my_app/i18n) - No dynamic imports that depend on working directory
- Avoid writing files next to the executable (use user data dirs)
See Tooling → Build & Distribute for runtime behavior and distribution patterns.
See Guides → App Settings for configuration and environment-aware settings.
Suggested PyInstaller command (starter)
A minimal starter command (adjust paths for your project):
pyinstaller -n MyApp --onedir --windowed -m my_app
To include assets and translations, you typically add --add-data entries, or a .spec file.
Example (Windows-style separator shown; on macOS/Linux use : instead of ;):
pyinstaller -n MyApp --onedir --windowed -m my_app ^
--add-data "src/my_app/assets;my_app/assets" ^
--add-data "src/my_app/i18n;my_app/i18n"
Tip
If you use a .spec file, treat it as part of your build system and keep it in version control.
What about “framework” modules?
As your app grows, you can introduce higher-level organization without changing the basics:
views/→ screens and navigationwidgets/→ reusable composite widgetsservices/→ IO and integration pointsstate.py→ shared signals and statesettings.py→ AppSettings / defaults
Start simple. Add structure when you feel friction.
Next steps
- Quick Start — build your first app
- App Structure — windows, layout, and state
- Layout — containers and composition
- CLI — scaffolding and build commands
- Build & Distribute — packaging for distribution