excel-orm 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,273 @@
1
+ Metadata-Version: 2.4
2
+ Name: excel-orm
3
+ Version: 0.1.0
4
+ Summary: A lightweight Excel ORM for generating templates and parsing typed row models.
5
+ Project-URL: Homepage, https://github.com/acdelrusso/excel-orm
6
+ Project-URL: Repository, https://github.com/acdelrusso/excel-orm
7
+ Project-URL: Issues, https://github.com/acdelrusso/excel-orm/issues
8
+ Author: Anthony Del Russo
9
+ License: MIT
10
+ Keywords: etl,excel,openpyxl,orm
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.13
18
+ Requires-Dist: openpyxl>=3.1.5
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Excel ORM
22
+
23
+ A lightweight, typed “Excel ORM” for generating Excel templates and parsing Excel workbooks into Python objects using column descriptors.
24
+
25
+ This project is designed for the common enterprise pattern where you:
26
+ 1) generate a structured `.xlsx` template for users,
27
+ 2) let users fill it in,
28
+ 3) load the workbook back into Python, producing typed objects grouped by model.
29
+
30
+ It uses `openpyxl` for reading/writing Excel files and supports multiple model “tables” on the same worksheet.
31
+
32
+ ---
33
+
34
+ ## Features
35
+
36
+ - **Typed column descriptors** (`text_column`, `int_column`, `bool_column`, `date_column`)
37
+ - **Template generation** with:
38
+ - merged **table title cells** (pluralized model name)
39
+ - bold headers
40
+ - sensible column widths
41
+ - multiple tables laid out horizontally on the same sheet with a configurable gap
42
+ - **Workbook parsing** into model-specific repositories:
43
+ - `excel_file.cars.all()` → `list[Car]`
44
+ - `excel_file.manufacturing_plants.all()` → `list[ManufacturingPlant]`
45
+ - **Validation hooks**
46
+ - column-level `not_null`
47
+ - optional row exclusion rules via `excludes`
48
+ - optional model-level `validate()` method
49
+
50
+ ---
51
+
52
+ ## Installation
53
+
54
+ ### From PyPI (once published)
55
+ ```bash
56
+ pip install excel-orm
57
+ ````
58
+
59
+ ### From source (uv)
60
+
61
+ ```bash
62
+ git clone <your-repo-url>
63
+ cd excel-orm
64
+ uv sync
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Quick Start
70
+
71
+ ### 1) Define models using `Column[...]` descriptors
72
+
73
+ ```python
74
+ from excel_orm.column import Column, text_column, int_column
75
+ from excel_orm.orm import ExcelFile, SheetSpec
76
+
77
+ class Car:
78
+ make: Column[str] = text_column(header="Make", not_null=True)
79
+ model: Column[str] = text_column(header="Model", not_null=True)
80
+ year: Column[int] = int_column(header="Year", not_null=True)
81
+
82
+ class ManufacturingPlant:
83
+ name: Column[str] = text_column(header="Factory Name", not_null=True)
84
+ location: Column[str] = text_column(header="Location")
85
+ ```
86
+
87
+ ### 2) Declare a sheet containing multiple models
88
+
89
+ Each model becomes its own table on the same worksheet.
90
+
91
+ ```python
92
+ sheet = SheetSpec(
93
+ name="Cars",
94
+ models=[Car, ManufacturingPlant],
95
+
96
+ # Layout rows
97
+ title_row=1,
98
+ header_row=2,
99
+ data_start_row=3,
100
+
101
+ # Horizontal spacing between model tables
102
+ template_table_gap=2,
103
+ )
104
+ ```
105
+
106
+ ### 3) Create an `ExcelFile`, generate a template, then load data
107
+
108
+ ```python
109
+ excel_file = ExcelFile(sheets=[sheet])
110
+
111
+ # Generate a blank template workbook
112
+ excel_file.generate_template("car_inventory_template.xlsx")
113
+
114
+ # Users fill in data in Excel...
115
+
116
+ # Load the filled workbook into repositories
117
+ excel_file.load_data("car_inventory_data.xlsx")
118
+
119
+ cars = excel_file.cars.all()
120
+ plants = excel_file.manufacturing_plants.all()
121
+
122
+ print(cars[0].make, cars[0].year)
123
+ print(plants[0].name, plants[0].location)
124
+ ```
125
+
126
+ ---
127
+
128
+ ## How It Works
129
+
130
+ ### Repositories
131
+
132
+ For each model you register, `ExcelFile` creates a repository attribute on the instance using a snake_case pluralized name:
133
+
134
+ * `Car` → `excel_file.cars`
135
+ * `ManufacturingPlant` → `excel_file.manufacturing_plants`
136
+
137
+ Repositories are simple list-like containers with an `all()` helper:
138
+
139
+ ```python
140
+ cars = excel_file.cars.all() # list[Car]
141
+ ```
142
+
143
+ ### Multi-table Sheets
144
+
145
+ A single worksheet can host multiple model tables. During template generation:
146
+
147
+ * A merged title cell is written above each table (pluralized class name in title case).
148
+ * Headers appear under the title.
149
+ * Data rows begin at `data_start_row`.
150
+ * Tables are placed horizontally with `template_table_gap` blank columns between them.
151
+
152
+ During parsing:
153
+
154
+ * The library locates each model table by matching the expected header sequence.
155
+ * It reads contiguous rows until a blank row is encountered.
156
+
157
+ ---
158
+
159
+ ## Column Types
160
+
161
+ ### Text
162
+
163
+ ```python
164
+ from excel_orm.column import Column, text_column
165
+
166
+ class Example:
167
+ name: Column[str] = text_column(header="Name", not_null=True, strip=True)
168
+ ```
169
+
170
+ * `None` parses to `""` (empty string).
171
+ * `strip=True` trims whitespace.
172
+
173
+ ### Integer
174
+
175
+ ```python
176
+ from excel_orm.column import Column, int_column
177
+
178
+ class Example:
179
+ qty: Column[int] = int_column(header="Qty", not_null=True)
180
+ ```
181
+
182
+ * `None` or `""` parses to `0`.
183
+
184
+ ### Boolean
185
+
186
+ ```python
187
+ from excel_orm.column import Column, bool_column
188
+
189
+ class Example:
190
+ active: Column[bool] = bool_column(header="Active")
191
+ ```
192
+
193
+ Accepted values include:
194
+
195
+ * True: `true, t, yes, y, 1` (case-insensitive)
196
+ * False: `false, f, no, n, 0`
197
+ * `None` / empty parses to `False`
198
+
199
+ Invalid values raise `ValueError`.
200
+
201
+ ### Date
202
+
203
+ ```python
204
+ from excel_orm.column import Column, date_column
205
+
206
+ class Example:
207
+ start_date: Column[date] = date_column(header="Start Date")
208
+ ```
209
+
210
+ The date parser supports:
211
+
212
+ * Excel-native `datetime`/`date` values from `openpyxl`
213
+ * ISO strings like `2025-06-01` and `2025-06-01T13:45:00`
214
+ * Common business formats including `01-JUN-2025`
215
+
216
+ Invalid/empty values raise `ValueError`.
217
+
218
+ ---
219
+
220
+ ## Validation
221
+
222
+ ### Column-level: `not_null`
223
+
224
+ ```python
225
+ class Car:
226
+ make: Column[str] = text_column(header="Make", not_null=True)
227
+ ```
228
+
229
+ If a `not_null=True` column parses to `None` or `""`, a `ValueError` is raised.
230
+
231
+ ### Row exclusion: `excludes`
232
+
233
+ If you set `excludes`, rows matching those raw values in that column will be skipped.
234
+
235
+ ```python
236
+ status: Column[str] = text_column(header="Status")
237
+ status.spec.excludes = {"IGNORE", "SKIP"} # example pattern
238
+ ```
239
+
240
+ (If you want a nicer API for excludes, consider adding it directly to the column factory signature.)
241
+
242
+ ### Model-level: `validate()`
243
+
244
+ If your model defines a `validate(self)` method, it is called after a row is parsed.
245
+
246
+ ```python
247
+ class Car:
248
+ make: Column[str] = text_column(header="Make", not_null=True)
249
+ year: Column[int] = int_column(header="Year", not_null=True)
250
+
251
+ def validate(self) -> None:
252
+ if self.year < 1886:
253
+ raise ValueError("Invalid car year")
254
+ ```
255
+
256
+ ---
257
+
258
+ ## Development
259
+
260
+ ### Run tests
261
+
262
+ ```bash
263
+ uv run pytest
264
+ ```
265
+
266
+ ### Lint/format (example)
267
+
268
+ If you use Ruff:
269
+
270
+ ```bash
271
+ uv run ruff check .
272
+ uv run ruff format .
273
+ ```
@@ -0,0 +1,3 @@
1
+ excel_orm-0.1.0.dist-info/METADATA,sha256=7LNtOt-gMqSJoaGEBRT4d1un3FEjjAxQVqbuG-OfY00,6714
2
+ excel_orm-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
3
+ excel_orm-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any