Skip to main content

A lightweight Excel ORM for generating templates and parsing typed row models.

Project description

Excel ORM

A lightweight, typed “Excel ORM” for generating Excel templates and parsing Excel workbooks into Python objects using column descriptors.

This project is designed for the common enterprise pattern where you:

  1. generate a structured .xlsx template for users,
  2. let users fill it in,
  3. load the workbook back into Python, producing typed objects grouped by model.

It uses openpyxl for reading/writing Excel files and supports multiple model “tables” on the same worksheet.


Features

  • Typed column descriptors (text_column, int_column, bool_column, date_column)
  • Template generation with:
    • merged table title cells (pluralized model name)
    • bold headers
    • sensible column widths
    • multiple tables laid out horizontally on the same sheet with a configurable gap
  • Workbook parsing into model-specific repositories:
    • excel_file.cars.all()list[Car]
    • excel_file.manufacturing_plants.all()list[ManufacturingPlant]
  • Validation hooks
    • column-level not_null
    • optional row exclusion rules via excludes
    • optional model-level validate() method

Installation

From PyPI (once published)

pip install excel-orm

From source (uv)

git clone <your-repo-url>
cd excel-orm
uv sync

Quick Start

1) Define models using Column[...] descriptors

from excel_orm.column import Column, text_column, int_column
from excel_orm.orm import ExcelFile, SheetSpec

class Car:
    make: Column[str] = text_column(header="Make", not_null=True)
    model: Column[str] = text_column(header="Model", not_null=True)
    year: Column[int] = int_column(header="Year", not_null=True)

class ManufacturingPlant:
    name: Column[str] = text_column(header="Factory Name", not_null=True)
    location: Column[str] = text_column(header="Location")

2) Declare a sheet containing multiple models

Each model becomes its own table on the same worksheet.

sheet = SheetSpec(
    name="Cars",
    models=[Car, ManufacturingPlant],

    # Layout rows
    title_row=1,
    header_row=2,
    data_start_row=3,

    # Horizontal spacing between model tables
    template_table_gap=2,
)

3) Create an ExcelFile, generate a template, then load data

excel_file = ExcelFile(sheets=[sheet])

# Generate a blank template workbook
excel_file.generate_template("car_inventory_template.xlsx")

# Users fill in data in Excel...

# Load the filled workbook into repositories
excel_file.load_data("car_inventory_data.xlsx")

cars = excel_file.cars.all()
plants = excel_file.manufacturing_plants.all()

print(cars[0].make, cars[0].year)
print(plants[0].name, plants[0].location)

How It Works

Repositories

For each model you register, ExcelFile creates a repository attribute on the instance using a snake_case pluralized name:

  • Carexcel_file.cars
  • ManufacturingPlantexcel_file.manufacturing_plants

Repositories are simple list-like containers with an all() helper:

cars = excel_file.cars.all()  # list[Car]

Multi-table Sheets

A single worksheet can host multiple model tables. During template generation:

  • A merged title cell is written above each table (pluralized class name in title case).
  • Headers appear under the title.
  • Data rows begin at data_start_row.
  • Tables are placed horizontally with template_table_gap blank columns between them.

During parsing:

  • The library locates each model table by matching the expected header sequence.
  • It reads contiguous rows until a blank row is encountered.

Column Types

Text

from excel_orm.column import Column, text_column

class Example:
    name: Column[str] = text_column(header="Name", not_null=True, strip=True)
  • None parses to "" (empty string).
  • strip=True trims whitespace.

Integer

from excel_orm.column import Column, int_column

class Example:
    qty: Column[int] = int_column(header="Qty", not_null=True)
  • None or "" parses to 0.

Boolean

from excel_orm.column import Column, bool_column

class Example:
    active: Column[bool] = bool_column(header="Active")

Accepted values include:

  • True: true, t, yes, y, 1 (case-insensitive)
  • False: false, f, no, n, 0
  • None / empty parses to False

Invalid values raise ValueError.

Date

from excel_orm.column import Column, date_column

class Example:
    start_date: Column[date] = date_column(header="Start Date")

The date parser supports:

  • Excel-native datetime/date values from openpyxl
  • ISO strings like 2025-06-01 and 2025-06-01T13:45:00
  • Common business formats including 01-JUN-2025

Invalid/empty values raise ValueError.


Validation

Column-level: not_null

class Car:
    make: Column[str] = text_column(header="Make", not_null=True)

If a not_null=True column parses to None or "", a ValueError is raised.

Row exclusion: excludes

If you set excludes, rows matching those raw values in that column will be skipped.

status: Column[str] = text_column(header="Status")
status.spec.excludes = {"IGNORE", "SKIP"}  # example pattern

(If you want a nicer API for excludes, consider adding it directly to the column factory signature.)

Model-level: validate()

If your model defines a validate(self) method, it is called after a row is parsed.

class Car:
    make: Column[str] = text_column(header="Make", not_null=True)
    year: Column[int] = int_column(header="Year", not_null=True)

    def validate(self) -> None:
        if self.year < 1886:
            raise ValueError("Invalid car year")

Development

Run tests

uv run pytest

Lint/format (example)

If you use Ruff:

uv run ruff check .
uv run ruff format .

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

excel_orm-0.1.2.tar.gz (41.4 kB view details)

Uploaded Source

Built Distribution

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

excel_orm-0.1.2-py3-none-any.whl (8.7 kB view details)

Uploaded Python 3

File details

Details for the file excel_orm-0.1.2.tar.gz.

File metadata

  • Download URL: excel_orm-0.1.2.tar.gz
  • Upload date:
  • Size: 41.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"20.04","id":"focal","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for excel_orm-0.1.2.tar.gz
Algorithm Hash digest
SHA256 435b65e1cf53c5214ed2a26c09e713ea47ebc03564f914d6365b3dcc9aa9b303
MD5 3aea6800bbdc2a0e26c52aeccdb909ba
BLAKE2b-256 f0c384aa6e48c2833b52f05251b0b6f844fca2e7519c16591e14920c1b600d80

See more details on using hashes here.

File details

Details for the file excel_orm-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: excel_orm-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 8.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"20.04","id":"focal","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for excel_orm-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 6b2f9e9c4bce63459e7434a6b73bf390b4757f0245f412a1429aaf8d30900c0d
MD5 21d0376673e63101f175b7d811b91840
BLAKE2b-256 465f8f8bb06a77b60ac4e4a6480637ab7e6cc39c4e2ed7a9c20d34417f165140

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