Skip to content

Commit 2156963

Browse files
authored
feat: Add description field to columns (#342)
1 parent 77609ae commit 2156963

17 files changed

Lines changed: 193 additions & 6 deletions

dataframely/columns/_base.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def __init__(
4949
check: Check | None = None,
5050
alias: str | None = None,
5151
metadata: dict[str, Any] | None = None,
52+
description: str | None = None,
5253
):
5354
"""
5455
Args:
@@ -79,6 +80,7 @@ def __init__(
7980
this option does _not_ allow to refer to the column with two different
8081
names, the specified alias is the only valid name.
8182
metadata: A dictionary of metadata to attach to the column.
83+
description: A human-readable description of the column.
8284
"""
8385
if nullable and primary_key:
8486
raise ValueError("Nullable primary key columns are not supported.")
@@ -89,6 +91,7 @@ def __init__(
8991
self.check = check
9092
self.alias = alias
9193
self.metadata = metadata
94+
self.description = description
9295
# The name may be overridden by the schema on column access.
9396
self._name = ""
9497

@@ -277,7 +280,10 @@ def _pydantic_field_kwargs(self) -> dict[str, Any]:
277280
Returns:
278281
A dictionary of kwargs to pass to pydantic.Field.
279282
"""
280-
return {}
283+
kwargs: dict[str, Any] = {}
284+
if self.description is not None:
285+
kwargs["description"] = self.description
286+
return kwargs
281287

282288
# ------------------------------------ HELPER ------------------------------------ #
283289

@@ -362,6 +368,17 @@ def with_metadata(self, metadata: dict[str, Any]) -> Self:
362368
"""
363369
return self.with_properties(metadata=metadata)
364370

371+
def with_description(self, description: str) -> Self:
372+
"""Return a new column definition with the specified description.
373+
374+
Args:
375+
description: A human-readable description of the column.
376+
377+
Returns:
378+
A new column instance with the specified description.
379+
"""
380+
return self.with_properties(description=description)
381+
365382
# ----------------------------------- SAMPLING ----------------------------------- #
366383

367384
def sample(self, generator: Generator, n: int = 1) -> pl.Series:
@@ -436,7 +453,7 @@ def as_dict(self, expr: pl.Expr) -> dict[str, Any]:
436453
else getattr(self, param)
437454
)
438455
for param in inspect.signature(self.__class__.__init__).parameters
439-
if param not in ("self", "alias")
456+
if param not in ("self", "alias", "description")
440457
},
441458
}
442459

@@ -485,8 +502,9 @@ def matches(self, other: Column, expr: pl.Expr) -> bool:
485502
for attr in attributes.parameters
486503
# NOTE: We do not want to compare the `alias` here as the comparison should
487504
# only evaluate the type and its constraints. Names are checked in
488-
# :meth:`Schema.matches`.
489-
if attr not in ("self", "alias")
505+
# :meth:`Schema.matches`. The `description` is also excluded as it is
506+
# human-readable documentation rather than a semantic constraint.
507+
if attr not in ("self", "alias", "description")
490508
)
491509

492510
def _attributes_match(
@@ -506,7 +524,9 @@ def __repr__(self) -> str:
506524
self.__class__.__init__
507525
).parameters.items()
508526
if attribute
509-
not in ["self", "alias"] # alias is always equal to the column name here
527+
# alias is always equal to the column name here; description is
528+
# human-readable documentation rather than a semantic constraint
529+
not in ["self", "alias", "description"]
510530
and not (
511531
# Do not include attributes that are set to their default value
512532
getattr(self, attribute) == param_details.default

dataframely/columns/any.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def __init__(
3030
check: Check | None = None,
3131
alias: str | None = None,
3232
metadata: dict[str, Any] | None = None,
33+
description: str | None = None,
3334
):
3435
"""
3536
Args:
@@ -53,13 +54,15 @@ def __init__(
5354
this option does _not_ allow to refer to the column with two different
5455
names, the specified alias is the only valid name.
5556
metadata: A dictionary of metadata to attach to the column.
57+
description: A human-readable description of the column.
5658
"""
5759
super().__init__(
5860
nullable=True,
5961
primary_key=False,
6062
check=check,
6163
alias=alias,
6264
metadata=metadata,
65+
description=description,
6366
)
6467

6568
@property

dataframely/columns/array.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __init__(
3939
check: Check | None = None,
4040
alias: str | None = None,
4141
metadata: dict[str, Any] | None = None,
42+
description: str | None = None,
4243
):
4344
"""
4445
Args:
@@ -69,6 +70,7 @@ def __init__(
6970
this option does _not_ allow to refer to the column with two different
7071
names, the specified alias is the only valid name.
7172
metadata: A dictionary of metadata to attach to the column.
73+
description: A human-readable description of the column.
7274
"""
7375
super().__init__(
7476
nullable=nullable,
@@ -77,6 +79,7 @@ def __init__(
7779
check=check,
7880
alias=alias,
7981
metadata=metadata,
82+
description=description,
8083
)
8184
self.inner = inner
8285
self.shape = shape if isinstance(shape, tuple) else (shape,)

dataframely/columns/categorical.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(
2727
check: Check | None = None,
2828
alias: str | None = None,
2929
metadata: dict[str, Any] | None = None,
30+
description: str | None = None,
3031
):
3132
"""
3233
Args:
@@ -59,6 +60,7 @@ def __init__(
5960
this option does _not_ allow to refer to the column with two different
6061
names, the specified alias is the only valid name.
6162
metadata: A dictionary of metadata to attach to the column.
63+
description: A human-readable description of the column.
6264
"""
6365
super().__init__(
6466
nullable=nullable,
@@ -67,6 +69,7 @@ def __init__(
6769
check=check,
6870
alias=alias,
6971
metadata=metadata,
72+
description=description,
7073
)
7174

7275
@property

dataframely/columns/datetime.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def __init__(
4646
check: Check | None = None,
4747
alias: str | None = None,
4848
metadata: dict[str, Any] | None = None,
49+
description: str | None = None,
4950
):
5051
"""
5152
Args:
@@ -88,6 +89,7 @@ def __init__(
8889
this option does _not_ allow to refer to the column with two different
8990
names, the specified alias is the only valid name.
9091
metadata: A dictionary of metadata to attach to the column.
92+
description: A human-readable description of the column.
9193
"""
9294
if resolution is not None:
9395
offset_time = pl.Series([EPOCH_DATETIME]).dt.offset_by(resolution).dt.time()
@@ -117,6 +119,7 @@ def __init__(
117119
check=check,
118120
alias=alias,
119121
metadata=metadata,
122+
description=description,
120123
)
121124
self.resolution = resolution
122125

@@ -188,6 +191,7 @@ def __init__(
188191
check: Check | None = None,
189192
alias: str | None = None,
190193
metadata: dict[str, Any] | None = None,
194+
description: str | None = None,
191195
):
192196
"""
193197
Args:
@@ -230,6 +234,7 @@ def __init__(
230234
this option does _not_ allow to refer to the column with two different
231235
names, the specified alias is the only valid name.
232236
metadata: A dictionary of metadata to attach to the column.
237+
description: A human-readable description of the column.
233238
"""
234239
if resolution is not None:
235240
offset_date = pl.Series([EPOCH_DATETIME]).dt.offset_by(resolution).dt.date()
@@ -259,6 +264,7 @@ def __init__(
259264
check=check,
260265
alias=alias,
261266
metadata=metadata,
267+
description=description,
262268
)
263269
self.resolution = resolution
264270

@@ -338,6 +344,7 @@ def __init__(
338344
check: Check | None = None,
339345
alias: str | None = None,
340346
metadata: dict[str, Any] | None = None,
347+
description: str | None = None,
341348
):
342349
"""
343350
Args:
@@ -384,6 +391,7 @@ def __init__(
384391
this option does _not_ allow to refer to the column with two different
385392
names, the specified alias is the only valid name.
386393
metadata: A dictionary of metadata to attach to the column.
394+
description: A human-readable description of the column.
387395
"""
388396
if resolution is not None and min is not None:
389397
if not datetime_matches_resolution(min, resolution):
@@ -409,6 +417,7 @@ def __init__(
409417
check=check,
410418
alias=alias,
411419
metadata=metadata,
420+
description=description,
412421
)
413422
self.resolution = resolution
414423
self.time_zone = time_zone
@@ -509,6 +518,7 @@ def __init__(
509518
check: Check | None = None,
510519
alias: str | None = None,
511520
metadata: dict[str, Any] | None = None,
521+
description: str | None = None,
512522
):
513523
"""
514524
Args:
@@ -552,6 +562,7 @@ def __init__(
552562
this option does _not_ allow to refer to the column with two different
553563
names, the specified alias is the only valid name.
554564
metadata: A dictionary of metadata to attach to the column.
565+
description: A human-readable description of the column.
555566
"""
556567
if resolution is not None and min is not None:
557568
if not timedelta_matches_resolution(min, resolution):
@@ -577,6 +588,7 @@ def __init__(
577588
check=check,
578589
alias=alias,
579590
metadata=metadata,
591+
description=description,
580592
)
581593
self.resolution = resolution
582594
self.time_unit = time_unit

dataframely/columns/decimal.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def __init__(
3737
check: Check | None = None,
3838
alias: str | None = None,
3939
metadata: dict[str, Any] | None = None,
40+
description: str | None = None,
4041
):
4142
"""
4243
Args:
@@ -77,6 +78,7 @@ def __init__(
7778
this option does _not_ allow to refer to the column with two different
7879
names, the specified alias is the only valid name.
7980
metadata: A dictionary of metadata to attach to the column.
81+
description: A human-readable description of the column.
8082
"""
8183
if isinstance(min, int):
8284
min = decimal.Decimal(min)
@@ -107,6 +109,7 @@ def __init__(
107109
check=check,
108110
alias=alias,
109111
metadata=metadata,
112+
description=description,
110113
)
111114
self.precision = precision
112115
self.scale = scale

dataframely/columns/enum.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def __init__(
3232
check: Check | None = None,
3333
alias: str | None = None,
3434
metadata: dict[str, Any] | None = None,
35+
description: str | None = None,
3536
):
3637
"""
3738
Args:
@@ -66,6 +67,7 @@ def __init__(
6667
this option does _not_ allow to refer to the column with two different
6768
names, the specified alias is the only valid name.
6869
metadata: A dictionary of metadata to attach to the column.
70+
description: A human-readable description of the column.
6971
"""
7072
super().__init__(
7173
nullable=nullable,
@@ -74,6 +76,7 @@ def __init__(
7476
check=check,
7577
alias=alias,
7678
metadata=metadata,
79+
description=description,
7780
)
7881
if isclass(categories) and issubclass(categories, enum.Enum):
7982
categories = (item.value for item in categories)

dataframely/columns/float.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __init__(
3939
check: Check | None = None,
4040
alias: str | None = None,
4141
metadata: dict[str, Any] | None = None,
42+
description: str | None = None,
4243
):
4344
"""
4445
Args:
@@ -79,6 +80,7 @@ def __init__(
7980
this option does _not_ allow to refer to the column with two different
8081
names, the specified alias is the only valid name.
8182
metadata: A dictionary of metadata to attach to the column.
83+
description: A human-readable description of the column.
8284
"""
8385
if min is not None and min < self.min_value:
8486
raise ValueError("Minimum value is too small for the data type.")
@@ -99,6 +101,7 @@ def __init__(
99101
check=check,
100102
alias=alias,
101103
metadata=metadata,
104+
description=description,
102105
)
103106

104107
@classproperty

dataframely/columns/integer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def __init__(
3535
check: Check | None = None,
3636
alias: str | None = None,
3737
metadata: dict[str, Any] | None = None,
38+
description: str | None = None,
3839
):
3940
"""
4041
Args:
@@ -75,6 +76,7 @@ def __init__(
7576
this option does _not_ allow to refer to the column with two different
7677
names, the specified alias is the only valid name.
7778
metadata: A dictionary of metadata to attach to the column.
79+
description: A human-readable description of the column.
7880
"""
7981
if min is not None and min < self.min_value:
8082
raise ValueError("`min` is too small for the data type.")
@@ -97,6 +99,7 @@ def __init__(
9799
check=check,
98100
alias=alias,
99101
metadata=metadata,
102+
description=description,
100103
)
101104

102105
@classproperty

dataframely/columns/list.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def __init__(
4141
min_length: int | None = None,
4242
max_length: int | None = None,
4343
metadata: dict[str, Any] | None = None,
44+
description: str | None = None,
4445
):
4546
"""
4647
Args:
@@ -77,6 +78,7 @@ def __init__(
7778
this option does _not_ allow to refer to the column with two different
7879
names, the specified alias is the only valid name.
7980
metadata: A dictionary of metadata to attach to the column.
81+
description: A human-readable description of the column.
8082
"""
8183
super().__init__(
8284
nullable=nullable,
@@ -85,6 +87,7 @@ def __init__(
8587
check=check,
8688
alias=alias,
8789
metadata=metadata,
90+
description=description,
8891
)
8992
self.inner = inner
9093
self.min_length = min_length

0 commit comments

Comments
 (0)