Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

0.2.2 is the first 0.2.x release published to PyPI. The 0.2.0 and 0.2.1 tags exist in git, but their publish runs were cancelled, so upgrading from PyPI goes straight from 0.1.16 to 0.2.2. The notes below cover every change in that jump.

## 0.2.6

Added:
- `FieldLabel`: `Annotated` metadata declaring the display label of a field whose name does not read well to users, and `declared_field_label` to read it. Form and table builders derive every other label from the field name.

## 0.2.5

Fixed:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "python-introspect"
version = "0.2.5"
version = "0.2.6"
description = "Pure Python introspection toolkit for function signatures, dataclasses, and type hints"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
5 changes: 4 additions & 1 deletion src/python_introspect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
type resolution for framework-specific types (lazy configs, proxies, etc.)
"""

__version__ = "0.2.5"
__version__ = "0.2.6"

from .signature_analyzer import (
SignatureAnalyzer,
Expand Down Expand Up @@ -40,6 +40,7 @@
mark_enableable,
)
from .choices import AnnotationChoices, declared_annotation_choices
from .field_label import FieldLabel, declared_field_label
from .validation import (
AnnotatedDataclassValidationMixin,
AnnotationValidationError,
Expand Down Expand Up @@ -122,6 +123,8 @@
"validate_annotation_value",
"AnnotationChoices",
"declared_annotation_choices",
"FieldLabel",
"declared_field_label",
# Dataclass projection
"dataclass_from_mapping",
"project_dataclass",
Expand Down
29 changes: 29 additions & 0 deletions src/python_introspect/field_label.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Display labels declared as ``Annotated`` metadata."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Annotated, get_args, get_origin


@dataclass(frozen=True, slots=True)
class FieldLabel:
"""Display label for a field whose name does not read well to users.

Declared as ``Annotated[T, FieldLabel("...")]``. Interfaces derive every
other label from the field name and every tooltip from the field docstring.
"""

text: str


def declared_field_label(annotation: object) -> str | None:
"""Return the label declared on an ``Annotated`` annotation, if any."""

if get_origin(annotation) is not Annotated:
return None
_base, *metadata = get_args(annotation)
for item in metadata:
if isinstance(item, FieldLabel):
return item.text
return None
17 changes: 17 additions & 0 deletions tests/test_field_label.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from dataclasses import dataclass, fields
from typing import Annotated, get_type_hints

from python_introspect import FieldLabel, declared_field_label


@dataclass(frozen=True)
class _Row:
labelled: Annotated[int, FieldLabel("Shown Name")] = 0
plain: int = 0


def test_declared_field_label_reads_annotated_metadata_only():
hints = get_type_hints(_Row, include_extras=True)
labels = {field.name: declared_field_label(hints[field.name]) for field in fields(_Row)}

assert labels == {"labelled": "Shown Name", "plain": None}
Loading