This project was created primarily for educational and learning purposes.
While it is well-structured and could technically be used in production, it is not intended for commercialization.
The main goal is to explore and demonstrate best practices, patterns, and technologies in software development.
- Clone the repository
- Go to the repository folder and execute:
python -m venv venv - Execute in Windows:
venv\Scripts\activate - Execute in Linux/Mac:
source venv/bin/activate - Execute:
pip install -r requirements.txt - Execute:
pip install -r requirements.dev.txt - Execute:
pip install -r requirements.test.txt - Use
python app.pyorpython -m srcto execute the program
NOTE: Install pre-commit inside the repository folder.
- Once you're inside the virtual environment, let's install the hooks specified in the pre-commit. Execute:
pre-commit install - Now every time you try to commit, the pre-commit lint will run. If you want to do it manually, you can run the command:
pre-commit run --all-files
PythonTkinterBoilerplate is a starting point for building desktop applications with a graphical interface using Python and Tkinter.
The problem it solves: avoid repeating the same setup and architecture decisions every time a new desktop project is started. Instead of configuring linting, testing, logging, and project structure from scratch each time, this template has all of that already in place.
What it includes:
- Ruff for linting and formatting
- pre-commit hooks for enforcing code quality before every commit
- Pydantic v2 for data validation and modeling
- Logging configured per environment (development, production, testing)
- A hierarchy of custom exceptions with centralized error handling
- pytest configured with coverage, env variables, and parallel execution
How to use it: clone the repository, rename the package and its references to match your project, and replace the template logic (users, auth, sample views) with your own application logic.
- Python >= 3.11
- Tkinter
pydantic==2.11.9
python-dotenv==1.0.1
pre-commit==4.3.0
pip-audit==2.7.3
ruff==0.11.12
pytest==8.4.2
pytest-env==1.1.5
pytest-cov==4.1.0
pytest-timeout==2.3.1
pytest-xdist==3.5.0
pyinstaller==6.16.0
https://www.diegolibonati.com.ar/#/project/python-tkinter-boilerplate
- Go to the repository folder
- Execute:
python -m venv venv - Execute in Windows:
venv\Scripts\activate - Execute in Linux/Mac:
source venv/bin/activate - Execute:
pip install -r requirements.txt - Execute:
pip install -r requirements.test.txt - Execute:
pytest --log-cli-level=INFO
You can generate a standalone executable (.exe on Windows, or binary on Linux/Mac) using PyInstaller.
- Go to the repository folder
- Activate your virtual environment:
venv\Scripts\activate - Install build dependencies:
pip install -r requirements.build.txt - Create the executable:
pyinstaller app.spec
Alternatively, you can run the helper script: build.bat
- Go to the repository folder
- Activate your virtual environment:
source venv/bin/activate - Install build dependencies:
pip install -r requirements.build.txt - Create the executable:
pyinstaller app.spec
Alternatively, you can run the helper script: ./build.sh
You can check your dependencies for known vulnerabilities using pip-audit.
- Go to the repository folder
- Activate your virtual environment
- Execute:
pip install -r requirements.dev.txt - Execute:
pip-audit -r requirements.txt
ENVIRONMENT: Defines the application environment. Acceptsdevelopment,production, ortesting.ENV_NAME: A custom environment variable for template demonstration purposes.
ENVIRONMENT=development
ENV_NAME=template_value
python-tkinter-boilerplate/
├── src/
│ ├── configs/
│ │ ├── __init__.py
│ │ ├── default_config.py
│ │ ├── development_config.py
│ │ ├── production_config.py
│ │ ├── testing_config.py
│ │ └── logger_config.py
│ ├── data_access/
│ │ ├── __init__.py
│ │ └── user_dao.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── user_model.py
│ ├── services/
│ │ ├── __init__.py
│ │ ├── auth_service.py
│ │ └── hash_service.py
│ ├── constants/
│ │ ├── __init__.py
│ │ └── messages.py
│ ├── ui/
│ │ ├── __init__.py
│ │ ├── interface_app.py
│ │ ├── styles.py
│ │ ├── components/
│ │ │ ├── __init__.py
│ │ │ └── labeled_entry.py
│ │ └── views/
│ │ ├── __init__.py
│ │ ├── login_view.py
│ │ ├── register_view.py
│ │ └── main_view.py
│ ├── utils/
│ │ ├── dialogs.py
│ │ ├── error_handler.py
│ │ ├── exceptions_handler.py
│ │ └── __init__.py
│ ├── assets/
│ │ └── images/
│ ├── __init__.py
│ └── __main__.py
├── tests/
│ ├── test_configs/
│ │ ├── __init__.py
│ │ ├── test_development_config.py
│ │ ├── test_logger_config.py
│ │ ├── test_production_config.py
│ │ ├── test_testing_config.py
│ │ └── test_default_config.py
│ ├── test_constants/
│ │ ├── __init__.py
│ │ └── test_messages.py
│ ├── test_data_access/
│ │ ├── __init__.py
│ │ └── test_user_dao.py
│ ├── test_models/
│ │ ├── __init__.py
│ │ └── test_user_model.py
│ ├── test_services/
│ │ ├── __init__.py
│ │ ├── test_auth_service.py
│ │ └── test_hash_service.py
│ ├── test_ui/
│ │ ├── __init__.py
│ │ └── test_interface_app.py
│ ├── __init__.py
│ └── conftest.py
├── app.py
├── pyproject.toml
├── requirements.txt
├── requirements.dev.txt
├── requirements.test.txt
├── requirements.build.txt
├── app.spec
├── build.bat
├── build.sh
├── .env
├── .env.example.dev
├── .env.example.prod
├── .gitignore
├── .pre-commit-config.yaml
├── LICENSE
└── README.md
src-> Root directory of the source code. Contains the full application logic following a layered architecture pattern.configs-> Contains all configuration classes organized by environment (development, production, testing). Includes logging setup and application settings.data_access-> Implements the Repository/DAO pattern. Abstracts all data operations, making it easy to switch from in-memory storage to a real database without affecting other layers.models-> Defines Pydantic models for data validation and serialization.services-> Contains business logic and rules. Validates data, enforces constraints, and orchestrates operations between UI and data access layer.constants-> Holds static values like error codes and user messages.ui-> Contains the graphical interface logic, organized into views, components, and styles.ui/views-> Individual screen/window classes (login, register, main). Each view is a self-contained Tkinter Frame or Toplevel.ui/components-> Reusable UI widgets shared across multiple views (e.g., labeled entry fields).ui/styles.py-> Centralized visual theme configuration (colors, fonts, spacing).ui/interface_app.py-> The main application orchestrator. Manages navigation between views and coordinates user actions with services.utils-> Contains shared utilities for general-purpose helper functions used across multiple modules.assets-> Static files such as images and icons used by the application.tests-> Contains tests organized to mirror thesrc/structure.conftest.py-> Defines pytest fixtures for application setup and tests data.app.py-> The application entry point. Creates the Tkinter root window and initializes the application.pyproject.toml-> Unified project configuration for pytest, ruff, and project metadata.requirements.txt-> Lists production dependencies.requirements.dev.txt-> Lists development dependencies (pre-commit, pip-audit).requirements.test.txt-> Lists testing dependencies (pytest, pytest-env, etc.).requirements.build.txt-> Lists build dependencies (PyInstaller).app.spec-> PyInstaller configuration for generating standalone executables.
This project follows a Layered Architecture pattern, organizing code into distinct levels with clear responsibilities. Each layer only communicates with the layer directly below it.
┌─────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ (UI Views & Components) │
│ Handles user interactions and display │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ ORCHESTRATION LAYER │
│ (InterfaceApp) │
│ Coordinates views with business logic │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ BUSINESS LAYER │
│ (Services) │
│ Contains business logic and validations │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ DATA ACCESS LAYER │
│ (Repository) │
│ Abstracts data operations │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ DATA STORAGE │
│ (In-Memory / Database) │
└─────────────────────────────────────────────────────────────┘
- Separation of Concerns: Each layer has a single responsibility
- Testability: Layers can be tested independently
- Maintainability: Changes in one layer don't affect others
- Flexibility: Easy to swap implementations (e.g., change from in-memory to a real database)
User clicks "Login"
│
▼
LoginView (login_view.py) → Captures user input
│
▼
InterfaceApp (interface_app.py) → Handles navigation and coordination
│
▼
AuthService (auth_service.py) → Validates credentials (business rules)
│
▼
UserDAO (user_dao.py) → Retrieves user data
│
▼
In-Memory Dict → Stores/retrieves data
Purpose: Abstracts data access logic, providing a clean API for data operations. The business layer doesn't know how data is stored.
Location: src/data_access/user_dao.py
class UserDAO:
"""In-memory user storage. Replace with a real database implementation."""
def __init__(self) -> None:
self._users: dict[str, UserModel] = { ... }
def get_by_username(self, username: str) -> UserModel | None:
return self._users.get(username)
def exists(self, username: str) -> bool:
return username in self._users
def save(self, user: UserModel) -> None:
self._users[user.username] = userBenefit: If you switch from in-memory storage to SQLite, PostgreSQL, or any database, only the repository layer needs to change.
Purpose: Encapsulates business logic in a dedicated layer. The UI stays thin, and business rules are centralized.
Location: src/services/auth_service.py
class AuthService:
# The DAO is injected here because UserDAO uses an in-memory dictionary as the data store.
# If using a real database, the DAO would be imported and used directly inside each method
# without needing to initialize it in the constructor.
def __init__(self, dao: UserDAO) -> None:
self._dao = dao
def login(self, username: str, password: str) -> UserModel:
# Business rules: validate fields, check user exists, verify password
...
def register(self, username: str, password: str, confirm_password: str) -> bool:
# Business rules: validate fields, check duplicates, hash password
...Benefit: Business rules are in one place, not scattered across UI code.
Purpose: Defines a base structure that subclasses can customize by overriding specific parts.
Location: src/configs/
# default_config.py - Base template
class DefaultConfig:
def __init__(self) -> None:
# General
self.TZ = os.getenv("TZ", "America/Argentina/Buenos_Aires")
self.DEBUG = False
self.TESTING = False
# App
self.ENV_NAME = os.getenv("ENV_NAME", "python tkinter boilerplate")
# development_config.py - Customizes for development
class DevelopmentConfig(DefaultConfig):
def __init__(self) -> None:
super().__init__()
self.DEBUG = True
self.ENV = "development"
# production_config.py - Customizes for production
class ProductionConfig(DefaultConfig):
def __init__(self) -> None:
super().__init__()
self.DEBUG = False
self.ENV = "production"Benefit: Common configuration in one place; environments only override what's different.
Purpose: Builds complex UI elements from simpler, reusable components. Each component is self-contained and can be composed into larger views.
Location: src/ui/components/labeled_entry.py
class LabeledEntry(Frame):
def __init__(self, parent: Misc, label_text: str, styles: Styles, variable: StringVar, show: str = "") -> None:
super().__init__(parent, bg=styles.PRIMARY_COLOR)
# Creates a Label + Entry combination as a single reusable widget
...Usage in Views:
LabeledEntry(
parent=self,
label_text="Username",
styles=self._styles,
variable=self.text_username,
).grid(row=0, column=0, pady=(20, 5), sticky="ew")Benefit: Eliminates code duplication across views and ensures consistent styling.
If you need to connect a real database, create the appropriate configuration and modify the repository layer:
- Add your database library to
requirements.txt(e.g.,sqlite3,sqlalchemy,pymongo) - Create a database configuration file in
src/configs/(e.g.,database_config.py) - Update
src/data_access/user_dao.pyto use the database instead of in-memory storage - No changes needed in services or UI layers — that's the benefit of the layered architecture
- Create a new view file in
src/ui/views/(e.g.,settings_view.py) - The view should extend
Frame(for embedded views) orToplevel(for new windows) - Use existing components from
src/ui/components/or create new ones - Register the navigation in
src/ui/interface_app.py
- Create a new service file in
src/services/(e.g.,product_service.py) - If it needs data access, create a corresponding dao in
src/data_access/ - If it needs a data model, create one in
src/models/ - Connect it to the UI through
src/ui/interface_app.py
None at the moment.