diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a8ff158..ecad05c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,13 +55,18 @@ jobs: fail-fast: false matrix: python-version: ["3.8", "3.9", "3.10", "3.11"] - torch-version: ["1.9.*", "1.10.*", "1.11.*", "1.12.*", "1.13.*", "2.0.*"] - lightning-version: ["2.0.*"] + torch-version: ["1.9.*", "1.10.*", "1.11.*", "1.12.*", "1.13.*", "2.0.*", "2.1.*"] + lightning-version: ["2.0.*", "2.1.*"] exclude: + - python-version: "3.10" + torch-version: "1.8.*" - python-version: "3.10" torch-version: "1.9.*" - python-version: "3.10" torch-version: "1.10.*" + + - python-version: "3.11" + torch-version: "1.8.*" - python-version: "3.11" torch-version: "1.9.*" - python-version: "3.11" diff --git a/src/tensorneko/arch/binary_classifier.py b/src/tensorneko/arch/binary_classifier.py index 85d70b5..0cb4e01 100644 --- a/src/tensorneko/arch/binary_classifier.py +++ b/src/tensorneko/arch/binary_classifier.py @@ -1,18 +1,17 @@ -from abc import ABC -from typing import Optional, Union, Sequence, Dict +from typing import Optional, Union, Sequence, Dict, Any from torch import Tensor -from torch.nn import BCEWithLogitsLoss +from torch.nn import BCEWithLogitsLoss, Module from torch.optim import Adam from torchmetrics import Accuracy, F1Score, AUROC from ..neko_model import NekoModel -class BinaryClassifier(NekoModel, ABC): +class BinaryClassifier(NekoModel): - def __init__(self, model=None, learning_rate: float = 1e-4, distributed: bool = False): - super().__init__() + def __init__(self, name, model: Module, learning_rate: float = 1e-4, distributed: bool = False): + super().__init__(name) self.save_hyperparameters() self.model = model self.learning_rate = learning_rate @@ -23,8 +22,10 @@ def __init__(self, model=None, learning_rate: float = 1e-4, distributed: bool = self.auc_fn = AUROC(task="binary") @classmethod - def from_module(cls, model, learning_rate: float = 1e-4, distributed=False): - return cls(model, learning_rate, distributed) + def from_module(cls, model: Module, learning_rate: float = 1e-4, name: str = "binary_classifier", + distributed: bool = False + ): + return cls(name, model, learning_rate, distributed) def forward(self, x): return self.model(x) @@ -49,6 +50,10 @@ def validation_step(self, batch: Optional[Union[Tensor, Sequence[Tensor]]] = Non ) -> Dict[str, Tensor]: return self.step(batch) + def predict_step(self, batch: Tensor, batch_idx: int, dataloader_idx: Optional[int] = None) -> Any: + x, y = batch + return self(x) + def configure_optimizers(self): optimizer = Adam(self.parameters(), lr=self.learning_rate) return [optimizer] diff --git a/src/tensorneko/callback/gpu_stats_logger.py b/src/tensorneko/callback/gpu_stats_logger.py index 6018514..b78d65e 100644 --- a/src/tensorneko/callback/gpu_stats_logger.py +++ b/src/tensorneko/callback/gpu_stats_logger.py @@ -1,31 +1,68 @@ +from typing import Any + from lightning.pytorch import Callback, Trainer, LightningModule +from lightning.pytorch.utilities.types import STEP_OUTPUT class GpuStatsLogger(Callback): """Log GPU stats for each training epoch""" - def __init__(self, delay: float = 0.5): + def __init__(self, delay: float = 0.5, on_epoch: bool = True, on_step: bool = False): try: from gpumonitor.monitor import GPUStatMonitor except ImportError: raise ImportError("gpumonitor is required to use GPUStatsLogger") - self.monitor = GPUStatMonitor(delay=delay) + self.monitor_epoch = GPUStatMonitor(delay=delay) if on_epoch else None + self.monitor_step = GPUStatMonitor(delay=delay) if on_step else None + self.on_epoch = on_epoch + self.on_step = on_step + assert self.on_epoch or self.on_step, "on_epoch and on_step cannot be both False" def on_train_epoch_start(self, trainer: Trainer, pl_module: LightningModule) -> None: - self.monitor.reset() + if not self.on_epoch: + return + self.monitor_epoch.reset() def on_train_epoch_end(self, trainer: Trainer, pl_module: LightningModule) -> None: - for gpu in self.monitor.average_stats.gpus: + if not self.on_epoch: + return + for gpu in self.monitor_epoch.average_stats.gpus: + logged_info = { + f"gpu{gpu.index}_memory_used_epoch": gpu.memory_used / 1024, + f"gpu{gpu.index}_memory_total_epoch": gpu.memory_total / 1024, + f"gpu{gpu.index}_memory_util_epoch": gpu.memory_used / gpu.memory_total, + f"gpu{gpu.index}_temperature_epoch": float(gpu.temperature), + f"gpu{gpu.index}_utilization_epoch": gpu.utilization / 100, + f"gpu{gpu.index}_power_draw_epoch": float(gpu.power_draw), + f"gpu{gpu.index}_power_percentage_epoch": gpu.power_draw / gpu.power_limit, + f"gpu{gpu.index}_fan_speed_epoch": float(gpu.fan_speed) if gpu.fan_speed is not None else 0., + } + pl_module.logger.log_metrics(logged_info, step=trainer.global_step) + pl_module.log_dict(logged_info, logger=False, sync_dist=pl_module.distributed) + + def on_train_batch_start( + self, trainer: Trainer, pl_module: LightningModule, batch: Any, batch_idx: int + ) -> None: + if not self.on_step: + return + self.monitor_step.reset() + + def on_train_batch_end( + self, trainer: Trainer, pl_module: LightningModule, outputs: STEP_OUTPUT, batch: Any, batch_idx: int + ) -> None: + if not self.on_step: + return + for gpu in self.monitor_step.average_stats.gpus: logged_info = { - f"gpu{gpu.index}_memory_used": gpu.memory_used / 1024, - f"gpu{gpu.index}_memory_total": gpu.memory_total / 1024, - f"gpu{gpu.index}_memory_util": gpu.memory_used / gpu.memory_total, - f"gpu{gpu.index}_temperature": float(gpu.temperature), - f"gpu{gpu.index}_utilization": gpu.utilization / 100, - f"gpu{gpu.index}_power_draw": float(gpu.power_draw), - f"gpu{gpu.index}_power_percentage": gpu.power_draw / gpu.power_limit, - f"gpu{gpu.index}_fan_speed": float(gpu.fan_speed) if gpu.fan_speed is not None else 0., + f"gpu{gpu.index}_memory_used_step": gpu.memory_used / 1024, + f"gpu{gpu.index}_memory_total_step": gpu.memory_total / 1024, + f"gpu{gpu.index}_memory_util_step": gpu.memory_used / gpu.memory_total, + f"gpu{gpu.index}_temperature_step": float(gpu.temperature), + f"gpu{gpu.index}_utilization_step": gpu.utilization / 100, + f"gpu{gpu.index}_power_draw_step": float(gpu.power_draw), + f"gpu{gpu.index}_power_percentage_step": gpu.power_draw / gpu.power_limit, + f"gpu{gpu.index}_fan_speed_step": float(gpu.fan_speed) if gpu.fan_speed is not None else 0., } pl_module.logger.log_metrics(logged_info, step=trainer.global_step) pl_module.log_dict(logged_info, logger=False, sync_dist=pl_module.distributed) diff --git a/src/tensorneko/callback/system_stats_logger.py b/src/tensorneko/callback/system_stats_logger.py index f707607..5a8a116 100644 --- a/src/tensorneko/callback/system_stats_logger.py +++ b/src/tensorneko/callback/system_stats_logger.py @@ -1,22 +1,44 @@ +from typing import Any + from lightning.pytorch import Callback, Trainer, LightningModule +from lightning.pytorch.utilities.types import STEP_OUTPUT class SystemStatsLogger(Callback): """Log system stats for each training epoch""" - def __init__(self): + def __init__(self, on_epoch: bool = True, on_step: bool = False): try: import psutil except ImportError: raise ImportError("psutil is required to use SystemStatsLogger") self.psutil = psutil + self.on_epoch = on_epoch + self.on_step = on_step + assert self.on_epoch or self.on_step, "on_epoch and on_step cannot be both False" def on_train_epoch_end(self, trainer: Trainer, pl_module: LightningModule) -> None: + if not self.on_epoch: + return + cpu_usage = self.psutil.cpu_percent() + memory_usage = self.psutil.virtual_memory().percent + logged_info = { + "cpu_usage_epoch": cpu_usage, + "memory_usage_epoch": memory_usage + } + pl_module.logger.log_metrics(logged_info, step=trainer.global_step) + pl_module.log_dict(logged_info, logger=False, sync_dist=pl_module.distributed) + + def on_train_batch_end( + self, trainer: Trainer, pl_module: LightningModule, outputs: STEP_OUTPUT, batch: Any, batch_idx: int + ) -> None: + if not self.on_step: + return cpu_usage = self.psutil.cpu_percent() memory_usage = self.psutil.virtual_memory().percent logged_info = { - "cpu_usage": cpu_usage, - "memory_usage": memory_usage + "cpu_usage_step": cpu_usage, + "memory_usage_step": memory_usage } pl_module.logger.log_metrics(logged_info, step=trainer.global_step) pl_module.log_dict(logged_info, logger=False, sync_dist=pl_module.distributed) diff --git a/src/tensorneko/evaluation/fid.py b/src/tensorneko/evaluation/fid.py index b1f0b95..cb105aa 100644 --- a/src/tensorneko/evaluation/fid.py +++ b/src/tensorneko/evaluation/fid.py @@ -90,8 +90,8 @@ def compute(self, batch_size=128, num_workers=0, progress_bar: bool = False) -> if progress_bar: tqdm = import_tqdm_auto().tqdm - pred = tqdm(total=len(pred), desc="Forward predicted features") - true = tqdm(total=len(true), desc="Forward ground truth features") + pred = tqdm(pred, total=len(pred), desc="Forward predicted features") + true = tqdm(true, total=len(true), desc="Forward ground truth features") for batch in pred: self.fid.update(batch.to(self.device), real=False) diff --git a/src/tensorneko/neko_trainer.py b/src/tensorneko/neko_trainer.py index 9d889bb..f7ada1c 100644 --- a/src/tensorneko/neko_trainer.py +++ b/src/tensorneko/neko_trainer.py @@ -3,16 +3,20 @@ from time import time from typing import Optional, Union, List, Dict +from lightning.fabric.plugins.precision.precision import _PRECISION_INPUT +from lightning.fabric.utilities.types import _PATH from lightning.pytorch import Trainer, Callback from lightning.pytorch.accelerators import Accelerator from lightning.pytorch.callbacks import ModelCheckpoint, Checkpoint from lightning.pytorch.loggers import Logger, TensorBoardLogger -from lightning.pytorch.plugins import PLUGIN_INPUT from lightning.pytorch.profilers import Profiler from lightning.pytorch.strategies import Strategy from lightning.pytorch.trainer.connectors.accelerator_connector import _LITERAL_WARN -from lightning.fabric.plugins.precision.precision import _PRECISION_INPUT -from lightning.fabric.utilities.types import _PATH + +try: + from lightning.pytorch.plugins import PLUGIN_INPUT +except ImportError: + from lightning.pytorch.plugins import _PLUGIN_INPUT as PLUGIN_INPUT from .callback import NilCallback, LrLogger, EpochNumLogger, EpochTimeLogger, GpuStatsLogger, SystemStatsLogger diff --git a/src/tensorneko/util/__init__.py b/src/tensorneko/util/__init__.py index fa3929b..d0f168b 100644 --- a/src/tensorneko/util/__init__.py +++ b/src/tensorneko/util/__init__.py @@ -9,7 +9,7 @@ from .configuration import Configuration from .misc import reduce_dict_by, summarize_dict_by, with_printed_shape, is_bad_num, count_parameters, compose, \ generate_inf_seq, listdir, with_printed, ifelse, dict_add, as_list, identity, list_to_dict, circular_pad, \ - load_py, try_until_success + load_py, try_until_success, sample_indexes from .misc import get_tensorneko_path from .dispatched_misc import sparse2binary, binary2sparse from .reproducibility import Seed @@ -71,6 +71,7 @@ "circular_pad", "load_py", "try_until_success", + "sample_indexes", "download_file", "WindowMerger", ] diff --git a/src/tensorneko/util/configuration.py b/src/tensorneko/util/configuration.py index 49c0d1b..5c787da 100644 --- a/src/tensorneko/util/configuration.py +++ b/src/tensorneko/util/configuration.py @@ -1,10 +1,12 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any +from typing import Generic +from tensorneko_util.util.type import T -class Configuration(ABC): + +class Configuration(ABC, Generic[T]): """ Configuration base abstract class. @@ -55,7 +57,7 @@ def __iter__(self): return iter((*self.args, *self.kwargs.values())) @abstractmethod - def build(self) -> Any: + def build(self) -> T: """ A method to build an object. diff --git a/src/tensorneko/util/misc.py b/src/tensorneko/util/misc.py index 2f7169d..d6807fb 100644 --- a/src/tensorneko/util/misc.py +++ b/src/tensorneko/util/misc.py @@ -9,7 +9,7 @@ from torch.nn import Module from tensorneko_util.util.misc import generate_inf_seq, listdir, with_printed, ifelse, dict_add, as_list, \ - identity, list_to_dict, compose, circular_pad, load_py, try_until_success + identity, list_to_dict, compose, circular_pad, load_py, try_until_success, sample_indexes from .type import T, A @@ -165,3 +165,4 @@ def get_tensorneko_path() -> str: circular_pad = circular_pad load_py = load_py try_until_success = try_until_success +sample_indexes = sample_indexes diff --git a/src/tensorneko/util/type.py b/src/tensorneko/util/type.py index 4a51759..643fb4e 100644 --- a/src/tensorneko/util/type.py +++ b/src/tensorneko/util/type.py @@ -1,4 +1,3 @@ -from enum import Enum from typing import Callable, Union, List, Tuple, TypeVar import numpy as np diff --git a/src/tensorneko_util/backend/_tqdm.py b/src/tensorneko_util/backend/_tqdm.py index 6ccfb00..516e394 100644 --- a/src/tensorneko_util/backend/_tqdm.py +++ b/src/tensorneko_util/backend/_tqdm.py @@ -1,4 +1,5 @@ _is_tqdm_available = None +_is_mita_tqdm_available = None def import_tqdm(): @@ -35,3 +36,21 @@ def import_tqdm_auto(): return auto else: raise ImportError("tqdm is not installed. Please install it by `pip install tqdm`") + + +def import_mita_tqdm(): + global _is_mita_tqdm_available + if _is_mita_tqdm_available is None: + try: + from mita_client import mita_tqdm + _is_mita_tqdm_available = True + return mita_tqdm + except ImportError: + _is_mita_tqdm_available = False + raise ImportError("mita_client is not installed. Please install it by `pip install mita_client`") + else: + if _is_mita_tqdm_available: + from mita_client import mita_tqdm + return mita_tqdm + else: + raise ImportError("mita_client is not installed. Please install it by `pip install mita_client`") diff --git a/src/tensorneko_util/preprocess/crop.py b/src/tensorneko_util/preprocess/crop.py index c4835f4..6bc83a8 100644 --- a/src/tensorneko_util/preprocess/crop.py +++ b/src/tensorneko_util/preprocess/crop.py @@ -60,7 +60,7 @@ def crop_with_padding(image: ndarray, x1: int, x2: int, y1: int, y2: int, pad_va @dispatch -def crop_with_padding(image: ndarray, x1: np.int32, x2: np.int32, y1: np.int32, y2: np.int32, +def crop_with_padding(image: ndarray, x1: ndarray, x2: ndarray, y1: ndarray, y2: ndarray, pad_value: Union[int, float] = 0., batch: bool = False ) -> ndarray: return crop_with_padding(image, int(x1), int(x2), int(y1), int(y2), pad_value, batch) diff --git a/src/tensorneko_util/preprocess/crop.pyi b/src/tensorneko_util/preprocess/crop.pyi index 87a07a1..24d17d5 100644 --- a/src/tensorneko_util/preprocess/crop.pyi +++ b/src/tensorneko_util/preprocess/crop.pyi @@ -7,3 +7,9 @@ from numpy import ndarray def crop_with_padding(image: ndarray, x1: int, x2: int, y1: int, y2: int, pad_value: Union[int, float] = 0., batch: bool = False ) -> ndarray: ... + + +@overload +def crop_with_padding(image: ndarray, x1: ndarray, x2: ndarray, y1: ndarray, y2: ndarray, + pad_value: Union[int, float] = 0., batch: bool = False +) -> ndarray: ... diff --git a/src/tensorneko_util/util/__init__.py b/src/tensorneko_util/util/__init__.py index af9a28f..4c1be41 100644 --- a/src/tensorneko_util/util/__init__.py +++ b/src/tensorneko_util/util/__init__.py @@ -3,7 +3,7 @@ from .dispatcher import dispatch from .fp import __, F, _, Stream, return_option, Option, Monad, Eval, Seq, AbstractSeq, curry from .misc import generate_inf_seq, compose, listdir, with_printed, ifelse, dict_add, as_list, identity, list_to_dict, \ - get_tensorneko_util_path, circular_pad, load_py, try_until_success + get_tensorneko_util_path, circular_pad, load_py, try_until_success, sample_indexes from .dispatched_misc import sparse2binary, binary2sparse from .ref import ref from .timer import Timer @@ -50,6 +50,7 @@ "circular_pad", "load_py", "try_until_success", + "sample_indexes", "download_file", "WindowMerger", ] diff --git a/src/tensorneko_util/util/dispatcher.py b/src/tensorneko_util/util/dispatcher.py index 45b3cf0..1f202f4 100644 --- a/src/tensorneko_util/util/dispatcher.py +++ b/src/tensorneko_util/util/dispatcher.py @@ -2,7 +2,7 @@ import inspect import warnings -from typing import Callable, Dict, List, Generic, Sequence, Optional +from typing import Callable, Dict, List, Generic, Sequence, Optional, TYPE_CHECKING, overload from .type import T @@ -223,3 +223,7 @@ def add(x: str, y: str) -> str: dispatch = DispatcherDecorator() + +# used for type hint, only `typing.overload` can be used for type hint +if TYPE_CHECKING: + dispatch = overload diff --git a/src/tensorneko_util/util/eventbus/event.py b/src/tensorneko_util/util/eventbus/event.py index 342de91..7d96749 100644 --- a/src/tensorneko_util/util/eventbus/event.py +++ b/src/tensorneko_util/util/eventbus/event.py @@ -6,9 +6,10 @@ class EventMeta(type): - def __call__(cls, *args, bus=EventBus.default, **kwargs): + def __call__(cls, *args, **kwargs): event = super().__call__(*args, **kwargs) - bus.emit(event, blocking=_blocking_flag) + event.bus = kwargs.get("bus", EventBus.default) + event.bus.emit(event, blocking=_blocking_flag) return event @@ -29,5 +30,9 @@ def no_blocking(): class Event(metaclass=EventMeta): bus: EventBus + def __init_subclass__(cls, **kwargs): + if "bus" in cls.__init__.__annotations__: + raise TypeError("`bus` parameter is preserved. It should not be annotated in the __init__ method") + E = TypeVar("E", bound=Event) diff --git a/src/tensorneko_util/util/misc.py b/src/tensorneko_util/util/misc.py index d3680d9..3d4d520 100644 --- a/src/tensorneko_util/util/misc.py +++ b/src/tensorneko_util/util/misc.py @@ -3,11 +3,13 @@ import time from functools import reduce from os.path import dirname, abspath -from typing import Callable, List, Dict, Iterable, Sequence, Any, Optional, Type, TypeVar, Union, Tuple +from types import ModuleType +from typing import Callable, List, Dict, Iterable, Sequence, Any, Optional, Type, Union, Tuple + import numpy as np from .fp import F, _, Stream -from .type import T, R +from .type import T, R, T_E def identity(*args, **kwargs): @@ -21,15 +23,15 @@ def identity(*args, **kwargs): return args -def generate_inf_seq(items: Iterable[Any]) -> Stream: +def generate_inf_seq(items: Iterable[T]) -> Stream[T]: """ Generate an infinity late-evaluate sequence. Args: - items [``Iterable[Any]``]: Original items. + items [``Iterable[T]``]: Original items. Returns: - :class:`~fp.stream.Stream`: The infinity sequence. + :class:`~fp.stream.Stream[T]`: The infinity sequence. Examples:: @@ -103,16 +105,16 @@ def listdir(path: str, filter_func: Callable[[str], bool] = lambda arg: True) -> return list(map(F(os.path.join, path), files)) -def with_printed(x: Any, func: Callable = identity) -> Any: +def with_printed(x: T, func: Callable[[T], Any] = identity) -> T: """ An identity function but with printed to console with some transform. Args: - x (``Any``): Input. - func (``Callable``, optional): A function used to apply the input for printing. + x (``T``): Input. + func (``(T) -> Any``, optional): A function used to apply the input for printing. Default: identity function. Returns: - ``Any``: Identity output. + ``T``: Identity output. Examples:: @@ -179,15 +181,15 @@ def as_list(*args, **kwargs) -> list: return list(args) + list(kwargs.values()) -def list_to_dict(l: List[T], key: Callable[[T], Any]) -> Dict[Any, T]: +def list_to_dict(l: List[T], key: Callable[[T], R]) -> Dict[R, T]: """ Convert the list as a dictionary by given key. Args: l (``List[T]``): Input list. - key (``(T) -> Any``): The key getter function. + key (``(T) -> R``): The key getter function. Returns: - ``Dict[Any, T]``: The output dictionary. + ``Dict[R, T]``: The output dictionary. """ return {key(x): x for x in l} @@ -202,16 +204,16 @@ def get_tensorneko_util_path() -> str: return dirname(dirname(abspath(__file__))) -def circular_pad(x: List, target: int) -> List: +def circular_pad(x: List[T], target: int) -> List[T]: """ Circular padding a list to the target length. Args: - x(``List``): Input list. + x(``List[T]``): Input list. target(``int``): Target length. Returns: - ``List``: The padded list. + ``List[T]``: The padded list. """ if len(x) == target: return x @@ -223,7 +225,7 @@ def circular_pad(x: List, target: int) -> List: return circular_pad(x + x, target) -def load_py(path: str) -> Any: +def load_py(path: str) -> ModuleType: """ Load a python file as a module. @@ -239,9 +241,6 @@ def load_py(path: str) -> Any: return module -T_E = TypeVar("T_E", bound=Exception) - - def try_until_success(func: Callable[[Any], T], *args, max_trials: Optional[int] = None, sleep_time: int = 0, exception_type: Union[Type[T_E], Tuple[T_E, ...]] = Exception, **kwargs ) -> T: diff --git a/src/tensorneko_util/util/server.py b/src/tensorneko_util/util/server.py index 44cb7ff..f56e833 100644 --- a/src/tensorneko_util/util/server.py +++ b/src/tensorneko_util/util/server.py @@ -1,16 +1,10 @@ from __future__ import annotations -from abc import ABC, abstractmethod -from typing import List - import http.server -import os -import shutil -import subprocess +from abc import ABC, abstractmethod from http.server import HTTPServer -from pathlib import Path from threading import Thread -from typing import Optional, Union, List +from typing import Optional, List class AbstractServer(ABC): diff --git a/src/tensorneko_util/util/timer.py b/src/tensorneko_util/util/timer.py index d325a87..acbf93e 100644 --- a/src/tensorneko_util/util/timer.py +++ b/src/tensorneko_util/util/timer.py @@ -1,7 +1,11 @@ +from __future__ import annotations + import time from functools import wraps from typing import Callable, Optional +from .type import T + class Timer: """ @@ -30,6 +34,12 @@ def f(): time.sleep(1) print("f") + # without parentheses + @Timer + def g(): + time.sleep(1) + print("f") + # disable verbose and get time manually with Timer(verbose=False) as t: time.sleep(1) @@ -50,6 +60,12 @@ def __init__(self, verbose: bool = True): self.total_time = None self.verbose = verbose + def __new__(cls, *args, **kwargs): + if len(args) > 0 and isinstance(args[0], Callable): + return cls()(args[0]) + else: + return super().__new__(cls) + def __enter__(self): self.times.append(time.perf_counter()) return self @@ -84,4 +100,3 @@ def wrapper(*args, **kwargs): @staticmethod def _make_str(name: str, t: float): return f"[Timer] {name}: {t} sec" - diff --git a/src/tensorneko_util/util/type.py b/src/tensorneko_util/util/type.py index 902f81e..021b9cb 100644 --- a/src/tensorneko_util/util/type.py +++ b/src/tensorneko_util/util/type.py @@ -8,6 +8,7 @@ R = TypeVar('R') # Return type P = TypeVar("P", int, float, str, bool) # Primitive type E = TypeVar("E", bound=Enum) +T_E = TypeVar("T_E", bound=Exception) # Exception type K = TypeVar("K") # Key type V = TypeVar("V") # Value type diff --git a/src/tensorneko_util/visualization/watcher/web/tsconfig.json b/src/tensorneko_util/visualization/watcher/web/tsconfig.json index 6217765..d092745 100644 --- a/src/tensorneko_util/visualization/watcher/web/tsconfig.json +++ b/src/tensorneko_util/visualization/watcher/web/tsconfig.json @@ -2,6 +2,7 @@ "extends": "@vue/tsconfig/tsconfig.web.json", "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "compilerOptions": { + "noImplicitAny": false, "importsNotUsedAsValues": "preserve", "baseUrl": ".", "paths": { diff --git a/version.txt b/version.txt index 09e9157..53b61ec 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.3.5 \ No newline at end of file +0.3.6 \ No newline at end of file