Instruction file imported from awieczork/theme-framework (
.github/instructions/package-architecture.instructions.md). Copyright stays with the author.
This file defines architectural rules for the theme_framework Python package. The governing principle is single-concern modules with unidirectional dependencies — every module owns one responsibility and imports flow strictly downward through a declared dependency graph.
<module_layout>
The package follows a flat module structure with one sub-package for export targets. Each module owns a single concern and communicates through typed interfaces — no module reaches into another's internals.
theme_framework/
__init__.py # Package version and public API re-exports
config.py # Pydantic BaseModel configs for palette, contrast, and export settings
palette.py # 12-step OKLCH Radix scale generation from primary hex
contrast.py # WCAG 2.1 AA + APCA dual contrast checking
semantic.py # Palette step → semantic role mapping
export/
__init__.py # Export dispatcher
vscode.py # VS Code JSON color theme generator
rstudio.py # RStudio .rstheme CSS generator
dbeaver.py # DBeaver Eclipse E4 XML generator
cli.py # Click or argparse CLI entry point
-
config— PydanticBaseModelconfigs withfrozen=Trueimmutability. All tunable parameters (thresholds, ratios, lightness targets) live here as typed fields with defaults -
palette— Derives complete 12-step OKLCH Radix scales (primary, neutral, accent) from a single hex input -
contrast— Checks WCAG 2.1 AA ratios and APCA perceptual contrast for foreground/background pairs -
semantic— Maps numbered palette steps to named semantic roles (e.g., step 1 → app background, step 9 → accent solid) -
export/— Format-specific generators that serialize semantic color maps into VS Code JSON, RStudio.rsthemeCSS, or DBeaver XML -
cli— Composition root that parses arguments, wires modules together, and drives the pipeline -
NEVER add business logic to
__init__.py— it re-exports public symbols and declares__version__only -
NEVER create nested sub-packages beyond
export/— keep the structure flat with one level of nesting maximum -
ALWAYS give each module a single responsibility matching its docstring — if a module needs a second concern, extract a new module
</module_layout>
<import_rules>
Dependencies flow in one direction: config → palette/contrast → semantic → export → cli. No cycles, no reverse imports.
config ← palette
config ← contrast
config ← semantic (+ palette output as function args)
config, semantic ← export/* (receive mapped colors, not raw palettes)
everything ← cli (composition root)
- NEVER import from
cliin any other module —cliis the terminal node in the dependency graph - NEVER import
palettefromcontrast— both depend onconfigandcoloraideindependently, keeping contrast checking decoupled from palette generation - NEVER pass raw palette arrays to export modules — export modules receive semantic color maps, not numbered steps
- ALWAYS pass palette output to
semanticas function arguments —semanticdepends onconfigfor role definitions but accepts palette data at call time, not import time - ALWAYS import
coloraidedirectly in modules that need color math (palette,contrast) — do not re-exportcoloraidethroughconfig - ALWAYS keep
configdependency-free within the package —configimports frompydanticand the standard library only, never from sibling modules
</import_rules>
<naming_conventions>
All names follow PEP 8. Consistency across the package matters more than individual preference.
-
Package and module names — all-lowercase with underscores only when needed for clarity:
theme_framework,config,palette,contrast,semantic -
Classes — CapWords:
PaletteConfig,ContrastResult,ThemeExporter,SemanticMap -
Functions — lowercase_with_underscores:
derive_palette,check_contrast,export_vscode,map_semantic_roles -
Constants — UPPER_CASE:
WCAG_AA_NORMAL,APCA_BODY_TEXT,DEFAULT_PRIMARY_HEX,RADIX_STEP_COUNT -
Private functions — single leading underscore:
_clamp_chroma,_fit_to_srgb,_interpolate_lightness -
NEVER use double leading underscores for name mangling — single underscore marks internal use, double underscores add complexity without benefit in this package
-
NEVER abbreviate module-level names beyond standard conventions —
cfginstead ofconfigobscures intent -
ALWAYS match the function name to its action:
derive_*for generation,check_*for validation,export_*for serialization,map_*for transformation
</naming_conventions>