From 2a204bc2c8b29ae4f9693b6a6e9767bc72e6d444 Mon Sep 17 00:00:00 2001 From: Alex Hadley Date: Wed, 27 Mar 2024 11:47:00 -0700 Subject: [PATCH] Change LoggedProp from a class to a type alias --- CHANGELOG.md | 4 ++++ datalogger/_logger.py | 41 +++++++++++++++-------------------------- docs/api-reference.md | 3 +++ docs/usage.md | 4 ++-- tests/test_logger.py | 11 ++++++++--- 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c965c6..45ab88e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- `LoggedProp` is now a type alias instead of a class. + ## [0.3.1] (Feb 7 2024) ### Added diff --git a/datalogger/_logger.py b/datalogger/_logger.py index d09e483..ee68967 100644 --- a/datalogger/_logger.py +++ b/datalogger/_logger.py @@ -1,9 +1,8 @@ """Data logging class.""" from __future__ import annotations -from typing import TypeVar, Generic, Any, overload, get_type_hints, get_origin +from typing import TypeVar, Annotated, Any, overload, get_type_hints, get_origin from collections.abc import Callable, Sequence, Collection, Mapping -from abc import ABC, abstractmethod import os import sys from datetime import datetime, timezone @@ -20,35 +19,21 @@ except ImportError: PARAMDB_INSTALLED = False - _T = TypeVar("_T") # Any type variable _LT = TypeVar("_LT", DataLog, DictLog) # Log type variable +LoggedProp = Annotated[_T, "LoggedProp"] +""" +Used as a type hint to indicate that properties of a class should be logged by +:py:meth:`Logger.log_props`. +""" + def _now() -> datetime: """Return the current time as a ``datetime`` object in the current timezone.""" return datetime.now(timezone.utc).astimezone() -class LoggedProp(Generic[_T], ABC): - """ - Used as a type hint to indicate that properties of a class should be logged by - :py:meth:`Logger.log_props`. - - Note that this class is only meant to be used as a type hint, not instantiated. - """ - - @abstractmethod - def __get__( - self, instance: Any | None, owner: Any | None = None - ) -> _T: # pragma: no cover - ... - - @abstractmethod - def __set__(self, instance: Any, value: _T) -> None: # pragma: no cover - ... - - class Logger: """ Logger corresponding to a directory that generates log files and @@ -291,8 +276,9 @@ def log_props( Save a dictionary of the given object's properties and corresponding metadata in a JSON file, and return a :py:class:`DictLog` with this data and metadata. - Only properties that have been marked with a :py:class:`LoggedProp` type hint at - the top of the class definition will be saved. For example:: + Only properties that have been marked with a + :py:const:`~datalogger._logger.LoggedProp` type hint at the top of the class + definition will be saved. For example:: class Example: value: LoggedProp @@ -307,7 +293,7 @@ class Example: obj_class = type(obj) logged_props: dict[str, Any] = {} try: - type_hints = get_type_hints(obj_class) + type_hints = get_type_hints(obj_class, include_extras=True) except Exception as exc: python_version = f"{sys.version_info.major}.{sys.version_info.minor}" raise RuntimeError( @@ -315,7 +301,10 @@ class Example: f" class type hints are invalid in Python {python_version}" ) from exc for name, type_hint in type_hints.items(): - if type_hint is LoggedProp or get_origin(type_hint) is LoggedProp: + if ( + get_origin(type_hint) is Annotated + and type_hint.__metadata__[0] == "LoggedProp" + ): if hasattr(obj, name): logged_props[name] = getattr(obj, name) return self.log_dict( diff --git a/docs/api-reference.md b/docs/api-reference.md index f4d8f64..a1e5ead 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -10,7 +10,10 @@ All of the following can be imported from `datalogger`. ```{eval-rst} .. autoclass:: Logger + +.. py:currentmodule:: datalogger._logger .. autoclass:: LoggedProp +.. py:currentmodule:: datalogger ``` ## Load Log diff --git a/docs/usage.md b/docs/usage.md index 435c58f..ceb0a57 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -158,8 +158,8 @@ display_tree("data_logs") ### Property Logs Property logs automatically store the properties of an object within a dictionary log. -Only properties marked with the type hint {py:class}`LoggedProp` will be saved. We can -create a property log using {py:meth}`Logger.log_props`. +Only properties marked with the type hint {py:class}`~datalogger._logger.LoggedProp` will +be saved. We can create a property log using {py:meth}`Logger.log_props`. ```{note} {py:class}`LoggedProp` can optionally take in a type parameter representing the type of diff --git a/tests/test_logger.py b/tests/test_logger.py index 4cc7055..618977f 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -351,13 +351,18 @@ class LogPropsObj: ), ( { - "p1": LoggedProp[Union[int, str]], + "p1": LoggedProp[Union[int, str]], # type: ignore "p2": Optional[LoggedProp], - "p3": LoggedProp[Optional[str]], + "p3": LoggedProp[Optional[str]], # type: ignore }, {"p1": 123, "p2": False, "p3": None}, {"p1": 123, "p3": None}, ), + ( + {"p1": LoggedProp[int], "p2": bool, "p3": LoggedProp[str]}, + {"p2": False, "p3": "test"}, + {"p3": "test"}, + ), ], ) def test_log_props_type_hints( @@ -394,7 +399,7 @@ class LogPropsParent: p3: LoggedProp[str] class LogPropsObj(LogPropsParent): - p1: int # type: ignore + p1: int obj = LogPropsObj() obj.p1 = 123