aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJ08nY2025-02-17 12:08:03 +0100
committerJ08nY2025-02-17 12:08:03 +0100
commit67ccbf3f4792fe39f9c75e0546140930d6dccd40 (patch)
treebc5968022987518093bdf8377d8c1843666839f7
parentc06dc527b4f80bff4266a245482f9bf98eb38a28 (diff)
downloadsec-certs-67ccbf3f4792fe39f9c75e0546140930d6dccd40.tar.gz
sec-certs-67ccbf3f4792fe39f9c75e0546140930d6dccd40.tar.zst
sec-certs-67ccbf3f4792fe39f9c75e0546140930d6dccd40.zip
Document the backed status of datasets.
-rw-r--r--src/sec_certs/dataset/auxiliary_dataset_handling.py7
-rw-r--r--src/sec_certs/dataset/dataset.py14
-rw-r--r--src/sec_certs/dataset/json_path_dataset.py7
-rw-r--r--src/sec_certs/serialization/json.py51
4 files changed, 69 insertions, 10 deletions
diff --git a/src/sec_certs/dataset/auxiliary_dataset_handling.py b/src/sec_certs/dataset/auxiliary_dataset_handling.py
index dc6993df..cd67f929 100644
--- a/src/sec_certs/dataset/auxiliary_dataset_handling.py
+++ b/src/sec_certs/dataset/auxiliary_dataset_handling.py
@@ -33,6 +33,13 @@ class AuxiliaryDatasetHandler(ABC):
self.aux_datasets_dir = Path(aux_datasets_dir) if aux_datasets_dir is not None else None # type: ignore
@property
+ def is_backed(self) -> bool:
+ """
+ Returns whether the dataset is backed by a directory.
+ """
+ return self.aux_datasets_dir is not None
+
+ @property
def root_dir(self) -> Path:
if self.RELATIVE_DIR:
return self.aux_datasets_dir / Path(self.RELATIVE_DIR)
diff --git a/src/sec_certs/dataset/dataset.py b/src/sec_certs/dataset/dataset.py
index 70bf3830..7add7b52 100644
--- a/src/sec_certs/dataset/dataset.py
+++ b/src/sec_certs/dataset/dataset.py
@@ -71,6 +71,13 @@ class Dataset(Generic[CertSubType], ComplexSerializableType, ABC):
self._set_local_paths()
@property
+ def is_backed(self) -> bool:
+ """
+ Returns whether the dataset is backed by a directory.
+ """
+ return self.root_dir is not None
+
+ @property
def root_dir(self) -> Path:
"""
Directory that will hold the serialized dataset files.
@@ -95,13 +102,6 @@ class Dataset(Generic[CertSubType], ComplexSerializableType, ABC):
self._set_local_paths()
@property
- def is_backed(self) -> bool:
- """
- Returns whether the dataset is backed by a directory.
- """
- return self.root_dir is not None
-
- @property
@only_backed(throw=False)
def web_dir(self) -> Path:
"""
diff --git a/src/sec_certs/dataset/json_path_dataset.py b/src/sec_certs/dataset/json_path_dataset.py
index 8b9212b7..76df73f2 100644
--- a/src/sec_certs/dataset/json_path_dataset.py
+++ b/src/sec_certs/dataset/json_path_dataset.py
@@ -18,6 +18,13 @@ class JSONPathDataset(ComplexSerializableType, ABC):
self.json_path = Path(json_path) if json_path is not None else None
@property
+ def is_backed(self) -> bool:
+ """
+ Returns whether the dataset is backed by a JSON file.
+ """
+ return self.json_path is not None
+
+ @property
def json_path(self) -> Path | None:
return self._json_path
diff --git a/src/sec_certs/serialization/json.py b/src/sec_certs/serialization/json.py
index 69822836..c843638c 100644
--- a/src/sec_certs/serialization/json.py
+++ b/src/sec_certs/serialization/json.py
@@ -3,6 +3,7 @@ from __future__ import annotations
import copy
import gzip
import json
+import logging
from collections.abc import Callable
from datetime import date, datetime
from functools import wraps
@@ -12,12 +13,24 @@ from typing import Any, TypeVar, cast
T = TypeVar("T", bound="ComplexSerializableType")
TCallable = TypeVar("TCallable", bound=Callable[..., Any])
+logger = logging.getLogger(__name__)
+
class SerializationError(Exception):
pass
class ComplexSerializableType:
+ """
+ A class that can be serialized to json and thus a dictionary.
+
+ Direct inheritance from this class is required for the class to be serializable.
+ Only the `serialized_attributes` are serialized. If `__slots__` is defined, only those attributes are serialized.
+
+ .. note::
+ The `to_dict` and `from_dict` should be overridden if non-trivial types of attributes need to be serialized.
+ """
+
__slots__: tuple[str]
def __init__(self, *args, **kwargs):
@@ -103,8 +116,14 @@ class ComplexSerializableType:
return json.load(handle, cls=CustomJSONDecoder)
-# Decorator for serialization
def serialize(func: Callable) -> Callable:
+ """
+ Decorator to be used on instance methods of ComplexSerializableType child classes.
+ The decorated method will be serialized to json after execution.
+
+ Adds the `update_json` keyword argument to the decorated method. If set to False, the json will not be updated.
+ """
+
@wraps(func)
def _serialize(*args, **kwargs):
if not args or not issubclass(type(args[0]), ComplexSerializableType):
@@ -127,6 +146,14 @@ def serialize(func: Callable) -> Callable:
def only_backed(throw: bool = True):
+ """
+ Decorator to be used on instance methods of ComplexSerializableType child classes.
+ The decorated method will only be executed if the `root_dir` attribute is set.
+
+ :param bool throw: if True, will raise ValueError if `root_dir` is not set, defaults to True
+ Otherwise, just logs a warning and returns None.
+ """
+
def deco(func: TCallable) -> TCallable:
@wraps(func)
def _only_backed(*args, **kwargs):
@@ -134,6 +161,7 @@ def only_backed(throw: bool = True):
if throw:
raise ValueError(f"Method {func.__name__} can only be called on backed dataset.")
else:
+ logger.warning(f"Method {func.__name__} can only be called on backed dataset.")
return None
else:
return func(*args, **kwargs)
@@ -144,6 +172,17 @@ def only_backed(throw: bool = True):
def get_class_fullname(obj: Any) -> str:
+ """
+ Returns the full name of the class of the object.
+
+ Example:
+ >>> get_class_fullname(datetime.now())
+ 'datetime.datetime'
+
+
+ :param Any obj: object to get the class name from
+ :return str: full name of the class
+ """
klass = obj if isinstance(obj, type) else obj.__class__
module = klass.__module__
if module == "builtins":
@@ -152,6 +191,10 @@ def get_class_fullname(obj: Any) -> str:
class CustomJSONEncoder(json.JSONEncoder):
+ """
+ Custom JSONEncoder.
+ """
+
def default(self, obj):
if isinstance(obj, ComplexSerializableType):
return {**{"_type": get_class_fullname(obj)}, **obj.to_dict()}
@@ -172,8 +215,10 @@ class CustomJSONEncoder(json.JSONEncoder):
class CustomJSONDecoder(json.JSONDecoder):
"""
- Custom JSONDecoder. Any complex object that should be de-serializable must inherit directly from class
- ComplexSerializableType (nested inheritance does not currently work (because x.__subclassess__() prints only direct
+ Custom JSONDecoder.
+
+ Any complex object that should be de-serializable must inherit directly from class
+ `ComplexSerializableType` (nested inheritance does not currently work (because x.__subclassess__() prints only direct
subclasses. Any such class must implement methods to_dict() and from_dict(). These are used to drive serialization.
"""