Skip to content

Commit

Permalink
Change LoggedProp from a class to a type alias
Browse files Browse the repository at this point in the history
  • Loading branch information
alexhad6 committed Mar 27, 2024
1 parent 296b445 commit 2a204bc
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 31 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 15 additions & 26 deletions datalogger/_logger.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -307,15 +293,18 @@ 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(
f"cannot log properties of '{obj_class.__name__}' object because its"
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(
Expand Down
3 changes: 3 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions tests/test_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -394,7 +399,7 @@ class LogPropsParent:
p3: LoggedProp[str]

class LogPropsObj(LogPropsParent):
p1: int # type: ignore
p1: int

obj = LogPropsObj()
obj.p1 = 123
Expand Down

0 comments on commit 2a204bc

Please sign in to comment.