excel-orm 0.1.3__tar.gz → 0.1.5__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: excel-orm
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: A lightweight Excel ORM for generating templates and parsing typed row models.
5
5
  Project-URL: Homepage, https://github.com/acdelrusso/excel-orm
6
6
  Project-URL: Repository, https://github.com/acdelrusso/excel-orm
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "excel-orm"
3
- version = "0.1.3"
3
+ version = "0.1.5"
4
4
  description = "A lightweight Excel ORM for generating templates and parsing typed row models."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -1,4 +1,12 @@
1
- from .column import Column, ColumnSpec, bool_column, date_column, int_column, text_column
1
+ from .base import RowBase
2
+ from .column import (
3
+ Column,
4
+ ColumnSpec,
5
+ bool_column,
6
+ date_column,
7
+ int_column,
8
+ text_column,
9
+ )
2
10
  from .orm import ExcelFile, PivotSheetSpec, SheetSpec
3
11
 
4
12
  __all__ = [
@@ -6,6 +14,7 @@ __all__ = [
6
14
  "ColumnSpec",
7
15
  "ExcelFile",
8
16
  "PivotSheetSpec",
17
+ "RowBase",
9
18
  "SheetSpec",
10
19
  "bool_column",
11
20
  "date_column",
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ from excel_orm import Column
7
+
8
+
9
+ class RowBase:
10
+ __columns__: list[Column[Any]]
11
+
12
+ def __init__(self, **kwargs: Any) -> None:
13
+ self._values: dict[str, Any] = {}
14
+
15
+ for col in getattr(self, "__columns__", []):
16
+ if col.name is None:
17
+ continue
18
+ self._values[col.name] = col.spec.default
19
+
20
+ for k, v in kwargs.items():
21
+ setattr(self, k, v)
@@ -41,9 +41,9 @@ class Column[T]:
41
41
  @overload
42
42
  def __get__(self, obj: None, objtype: type[Owner] | None = None) -> Column[T]: ...
43
43
  @overload
44
- def __get__(self, obj: Owner, objtype: type[Owner] | None = None) -> T | None: ...
44
+ def __get__(self, obj: Owner, objtype: type[Owner] | None = None) -> T: ...
45
45
 
46
- def __get__(self, obj: Owner | None, objtype: type[Owner] | None = None) -> T | Column | None:
46
+ def __get__(self, obj: Owner | None, objtype: type[Owner] | None = None) -> T | Column:
47
47
  if obj is None:
48
48
  return self
49
49
  if self.name is None:
@@ -71,7 +71,7 @@ def text_column(
71
71
  default: str | None = None,
72
72
  strip: bool = True,
73
73
  not_null: bool = False,
74
- ):
74
+ ) -> Column[str]:
75
75
  def parse(raw: Any) -> str:
76
76
  if raw is None:
77
77
  return ""
@@ -96,7 +96,7 @@ def int_column(
96
96
  *,
97
97
  default: int | None = None,
98
98
  not_null: bool = False,
99
- ):
99
+ ) -> Column[int]:
100
100
  def parse(raw: Any) -> int:
101
101
  if raw is None or raw == "":
102
102
  return 0
@@ -112,7 +112,7 @@ def int_column(
112
112
  )
113
113
 
114
114
 
115
- def bool_column(header: str | None = None, *, default: bool | None = None):
115
+ def bool_column(header: str | None = None, *, default: bool | None = None) -> Column[bool]:
116
116
  def parse(raw: Any) -> bool:
117
117
  if raw is None or raw == "":
118
118
  return False
@@ -134,7 +134,7 @@ def bool_column(header: str | None = None, *, default: bool | None = None):
134
134
  )
135
135
 
136
136
 
137
- def date_column(header: str | None = None, *, default: date | None = None):
137
+ def date_column(header: str | None = None, *, default: date | None = None) -> Column[date]:
138
138
  _DATE_FORMATS: tuple[str, ...] = (
139
139
  "%d-%b-%Y", # 01-JUN-2025 (your requirement)
140
140
  "%d-%b-%y", # 01-JUN-25
@@ -2,6 +2,7 @@ from datetime import date
2
2
 
3
3
  import pytest
4
4
 
5
+ from excel_orm.base import RowBase
5
6
  from src.excel_orm import (
6
7
  Column,
7
8
  ExcelFile,
@@ -15,12 +16,12 @@ from src.excel_orm import (
15
16
 
16
17
  @pytest.fixture
17
18
  def models():
18
- class Car:
19
+ class Car(RowBase):
19
20
  make: Column[str] = text_column(header="Make", not_null=True)
20
21
  model: Column[str] = text_column(header="Model", not_null=True)
21
22
  year: Column[int] = int_column(header="Year", not_null=True)
22
23
 
23
- class ManufacturingPlant:
24
+ class ManufacturingPlant(RowBase):
24
25
  name: Column[str] = text_column(header="Factory Name", not_null=True)
25
26
  location: Column[str] = text_column(header="Location")
26
27
 
@@ -53,7 +54,7 @@ def demand_model():
53
54
 
54
55
  @pytest.fixture
55
56
  def demand_two_pivot():
56
- class Demand:
57
+ class Demand(RowBase):
57
58
  dt: Column[date] = date_column(header="Date")
58
59
  region: Column[str] = text_column(header="Region", not_null=True)
59
60
  product: Column[str] = text_column(header="Product", not_null=True)
@@ -4,11 +4,18 @@ from datetime import date, datetime
4
4
 
5
5
  import pytest
6
6
 
7
- from src.excel_orm import Column, bool_column, date_column, int_column, text_column
7
+ from src.excel_orm import (
8
+ Column,
9
+ RowBase,
10
+ bool_column,
11
+ date_column,
12
+ int_column,
13
+ text_column,
14
+ )
8
15
 
9
16
 
10
17
  def test_descriptor_stores_in_values_dict():
11
- class Foo:
18
+ class Foo(RowBase):
12
19
  a: Column[str] = text_column(header="A")
13
20
 
14
21
  f = Foo()
@@ -20,14 +27,14 @@ def test_descriptor_stores_in_values_dict():
20
27
 
21
28
 
22
29
  def test_descriptor_class_level_access_returns_column():
23
- class Foo:
30
+ class Foo(RowBase):
24
31
  a: Column[str] = text_column(header="A")
25
32
 
26
33
  assert isinstance(Foo.a, Column)
27
34
 
28
35
 
29
36
  def test_not_null_validation_raises_on_none_or_empty():
30
- class Foo:
37
+ class Foo(RowBase):
31
38
  a: Column[str] = text_column(header="A", not_null=True)
32
39
 
33
40
  f = Foo()
@@ -108,3 +115,4 @@ def test_date_column_invalid_raises():
108
115
  col = date_column(header="D")
109
116
  with pytest.raises(ValueError):
110
117
  col.parse_cell("not-a-date")
118
+ col.parse_cell(" ")
@@ -5,7 +5,7 @@ from datetime import date, datetime
5
5
  import pytest
6
6
  from openpyxl import load_workbook
7
7
 
8
- from excel_orm import Column, ExcelFile, PivotSheetSpec, text_column
8
+ from excel_orm import Column, ExcelFile, PivotSheetSpec, RowBase, text_column
9
9
  from excel_orm.orm import SheetSpec
10
10
 
11
11
 
@@ -311,7 +311,7 @@ def test_pivot_two_row_fields_template_and_parse_end_to_end(tmp_path, demand_two
311
311
  def test_duplicate_header_names_do_not_clash(tmp_path, models):
312
312
  Car, _ = models
313
313
 
314
- class ManufacturingPlant:
314
+ class ManufacturingPlant(RowBase):
315
315
  name: Column[str] = text_column(header="Make", not_null=True)
316
316
  location: Column[str] = text_column(header="location")
317
317
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from excel_orm import RowBase
3
4
  from src.excel_orm import Column, int_column, text_column
4
5
  from src.excel_orm.orm import (
5
6
  _camel_to_snake,
@@ -31,7 +32,7 @@ def test_repo_and_display_name():
31
32
 
32
33
 
33
34
  def test_get_model_columns_respects_annotation_order():
34
- class A:
35
+ class A(RowBase):
35
36
  x: Column[str] = text_column(header="X")
36
37
  y: Column[int] = int_column(header="Y")
37
38
 
@@ -40,7 +41,7 @@ def test_get_model_columns_respects_annotation_order():
40
41
 
41
42
 
42
43
  def test_instantiate_model_sets_defaults_and_requires_set_name():
43
- class A:
44
+ class A(RowBase):
44
45
  x: Column[str] = text_column(header="X", default="dflt")
45
46
 
46
47
  a = _instantiate_model(A)
@@ -187,7 +187,7 @@ wheels = [
187
187
 
188
188
  [[package]]
189
189
  name = "excel-orm"
190
- version = "0.1.3"
190
+ version = "0.1.5"
191
191
  source = { editable = "." }
192
192
  dependencies = [
193
193
  { name = "openpyxl" },
File without changes
File without changes
File without changes
File without changes