diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a026253..56dd36c 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -11,14 +11,13 @@ jobs: steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 - - uses: pre-commit/action@v2.0.3 + - uses: pre-commit/action@v3.0.0 test: name: Run Tests - if: ${{ !contains(github.event.pull_request.title, 'WIP') }} runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9] + python-version: ['3.8', '3.9', '3.10'] steps: - name: Check out the code uses: actions/checkout@v3 @@ -30,24 +29,33 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install Poetry - uses: snok/install-poetry@v1.1.1 + uses: snok/install-poetry@v1.3.3 + with: + version: 1.2.1 + + - name: Setup Poetry + run: | + poetry config virtualenvs.in-project true + + - name: Cache + id: cache + uses: actions/cache@v3.2.2 with: - version: 1.1.4 + path: '.venv' + key: run-tests-${{ hashFiles('poetry.lock') }} - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' run: | - poetry config virtualenvs.create false - pip install -U certifi + if [ -d ".venv" ]; then rm -rf .venv; fi + poetry run pip install -U certifi poetry install - name: Run Tests - run: pytest --cov=elegy --cov-report=term-missing --cov-report=xml + run: poetry run pytest --cov=ciclo --cov-report=term-missing --cov-report=xml - name: Upload coverage - uses: codecov/codecov-action@v1 - - - name: Test Examples - run: bash scripts/test-examples.sh + uses: codecov/codecov-action@v3.1.1 test-import: name: Test Import without Dev Dependencies @@ -55,7 +63,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9] + python-version: ['3.8', '3.9', '3.10'] steps: - name: Check out the code uses: actions/checkout@v3 @@ -67,15 +75,28 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install Poetry - uses: snok/install-poetry@v1.1.1 + uses: snok/install-poetry@v1.3.3 with: - version: 1.1.4 + version: 1.2.1 + + - name: Setup Poetry + run: | + poetry config virtualenvs.in-project true + + - name: Cache + id: cache + uses: actions/cache@v3.2.2 + with: + path: '.venv' + key: test-import-${{ hashFiles('poetry.lock') }} - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' run: | - pip install -U certifi - poetry config virtualenvs.create false + if [ -d ".venv" ]; then rm -rf .venv; fi + poetry run pip install -U certifi poetry install --no-dev - - name: Test Import Elegy - run: python -c "import elegy" + - name: Test Import + run: | + poetry run python -c "import ciclo" diff --git a/ciclo/callbacks.py b/ciclo/callbacks.py index c5b0bd5..586a958 100644 --- a/ciclo/callbacks.py +++ b/ciclo/callbacks.py @@ -6,7 +6,6 @@ from enum import Enum, auto from typing import Any, Callable, Dict, Optional, Tuple, Union, overload -from flax.training import checkpoints as flax_checkpoints from pkbar import Kbar from tqdm import tqdm @@ -24,16 +23,15 @@ from ciclo.utils import get_batch_size, is_scalar -# import wandb Run -def _get_Run(): - if importlib.util.find_spec("wandb") is not None: - from wandb.wandb_run import Run - else: - locals()["Run"] = Any - return Run +def unavailable_dependency(msg: str) -> Any: + class DependencyNotAvailable(LoopCallbackBase[S]): + def __init__(self, *args: Any, **kwargs: Any) -> None: + raise RuntimeError(msg) + def __loop_callback__(self, loop_state: LoopState[S]) -> CallbackOutput[S]: + raise RuntimeError(msg) -Run = _get_Run() + return DependencyNotAvailable class OptimizationMode(str, Enum): @@ -96,83 +94,95 @@ def __loop_callback__(self, loop_state: LoopState[S]) -> CallbackOutput[S]: return self(loop_state.state) -class checkpoint(LoopCallbackBase[S]): - def __init__( - self, - ckpt_dir: Union[str, os.PathLike], - prefix: str = "checkpoint_", - keep: int = 1, - overwrite: bool = False, - keep_every_n_steps: Optional[int] = None, - async_manager: Optional[flax_checkpoints.AsyncManager] = None, - monitor: Optional[str] = None, - mode: Union[str, OptimizationMode] = "min", - ): - if isinstance(mode, str): - mode = OptimizationMode[mode] - - if mode not in OptimizationMode: - raise ValueError( - f"Invalid mode: {mode}, expected one of {list(OptimizationMode)}" - ) - else: - self.mode = mode - - self.ckpt_dir = ckpt_dir - self.prefix = prefix - self.keep = keep - self.overwrite = overwrite - self.keep_every_n_steps = keep_every_n_steps - self.async_manager = async_manager - self.monitor = monitor - self.minimize = self.mode == OptimizationMode.min - self._best: Optional[float] = None - - def __call__( - self, elapsed: Elapsed, state: S, logs: Optional[LogsLike] = None - ) -> None: - save_checkpoint = True - step_or_metric = elapsed.steps - overwrite = self.overwrite +if importlib.util.find_spec("tensorflow") is not None: + from flax.training import checkpoints as flax_checkpoints + + class checkpoint(LoopCallbackBase[S]): + def __init__( + self, + ckpt_dir: Union[str, os.PathLike], + prefix: str = "checkpoint_", + keep: int = 1, + overwrite: bool = False, + keep_every_n_steps: Optional[int] = None, + async_manager: Optional[flax_checkpoints.AsyncManager] = None, + monitor: Optional[str] = None, + mode: Union[str, OptimizationMode] = "min", + ): + if isinstance(mode, str): + mode = OptimizationMode[mode] - if self.monitor is not None: - if logs is None: + if mode not in OptimizationMode: raise ValueError( - "checkpoint callback requires logs to monitor a metric" + f"Invalid mode: {mode}, expected one of {list(OptimizationMode)}" ) - if not isinstance(logs, Logs): - logs = Logs(logs) - - try: - value = logs.entry_value(self.monitor) - except KeyError: - raise ValueError(f"Monitored value '{self.monitor}' not found in logs") - - if ( - self._best is None - or (self.minimize and value < self._best) - or (not self.minimize and value > self._best) - ): - self._best = value - step_or_metric = value if self.mode == OptimizationMode.max else -value else: - save_checkpoint = False - - if save_checkpoint: - flax_checkpoints.save_checkpoint( - ckpt_dir=self.ckpt_dir, - target=state, - step=step_or_metric, - prefix=self.prefix, - keep=self.keep, - overwrite=overwrite, - keep_every_n_steps=self.keep_every_n_steps, - async_manager=self.async_manager, - ) + self.mode = mode + + self.ckpt_dir = ckpt_dir + self.prefix = prefix + self.keep = keep + self.overwrite = overwrite + self.keep_every_n_steps = keep_every_n_steps + self.async_manager = async_manager + self.monitor = monitor + self.minimize = self.mode == OptimizationMode.min + self._best: Optional[float] = None + + def __call__( + self, elapsed: Elapsed, state: S, logs: Optional[LogsLike] = None + ) -> None: + save_checkpoint = True + step_or_metric = elapsed.steps + overwrite = self.overwrite + + if self.monitor is not None: + if logs is None: + raise ValueError( + "checkpoint callback requires logs to monitor a metric" + ) + if not isinstance(logs, Logs): + logs = Logs(logs) + + try: + value = logs.entry_value(self.monitor) + except KeyError: + raise ValueError( + f"Monitored value '{self.monitor}' not found in logs" + ) + + if ( + self._best is None + or (self.minimize and value < self._best) + or (not self.minimize and value > self._best) + ): + self._best = value + step_or_metric = ( + value if self.mode == OptimizationMode.max else -value + ) + else: + save_checkpoint = False + + if save_checkpoint: + flax_checkpoints.save_checkpoint( + ckpt_dir=self.ckpt_dir, + target=state, + step=step_or_metric, + prefix=self.prefix, + keep=self.keep, + overwrite=overwrite, + keep_every_n_steps=self.keep_every_n_steps, + async_manager=self.async_manager, + ) - def __loop_callback__(self, loop_state: LoopState[S]) -> CallbackOutput[S]: - self(loop_state.elapsed, loop_state.state, loop_state.accumulated_logs) - return {}, loop_state.state + def __loop_callback__(self, loop_state: LoopState[S]) -> CallbackOutput[S]: + self(loop_state.elapsed, loop_state.state, loop_state.accumulated_logs) + return {}, loop_state.state + +else: + checkpoint = unavailable_dependency( + "'tensorflow' package is not available, please install it to use the 'checkpoint' callback" + ) class early_stopping(LoopCallbackBase[S]): @@ -443,25 +453,35 @@ def __loop_callback__(self, loop_state: LoopState[S]) -> CallbackOutput[S]: return {}, loop_state.state -class wandb_logger(LoopCallbackBase[S]): - def __init__(self, run: Run): - self.run = run +if importlib.util.find_spec("wandb") is not None: + from wandb.wandb_run import Run - def __call__(self, elapsed: Elapsed, logs: LogsLike) -> None: - data = {} - for collection, collection_logs in logs.items(): - for key, value in collection_logs.items(): - if is_scalar(value): - if key in data: - key = f"{collection}.{key}" - data[key] = value + class wandb_logger(LoopCallbackBase[S]): + def __init__(self, run: Run): + from wandb.wandb_run import Run - if len(data) > 0: - self.run.log(data, step=elapsed.steps) + self.run: Run = run - def __loop_callback__(self, loop_state: LoopState[S]) -> CallbackOutput[S]: - self(loop_state.elapsed, loop_state.logs) - return {}, loop_state.state + def __call__(self, elapsed: Elapsed, logs: LogsLike) -> None: + data = {} + for collection, collection_logs in logs.items(): + for key, value in collection_logs.items(): + if is_scalar(value): + if key in data: + key = f"{collection}.{key}" + data[key] = value + + if len(data) > 0: + self.run.log(data, step=elapsed.steps) + + def __loop_callback__(self, loop_state: LoopState[S]) -> CallbackOutput[S]: + self(loop_state.elapsed, loop_state.logs) + return {}, loop_state.state + +else: + wandb_logger = unavailable_dependency( + "'wandb' package is not available, please install it to use the 'wandb_logger' callback" + ) class NoOp(LoopCallbackBase[S]): diff --git a/poetry.lock b/poetry.lock index 39f3590..f0614ed 100644 --- a/poetry.lock +++ b/poetry.lock @@ -281,6 +281,20 @@ test = ["Pillow", "flake8", "isort", "matplotlib", "pytest"] test-minimal = ["pytest"] test-no-codebase = ["Pillow", "matplotlib", "pytest"] +[[package]] +name = "coverage" +version = "7.0.1" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "cycler" version = "0.11.0" @@ -1594,6 +1608,21 @@ tomli = ">=1.0.0" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -1679,11 +1708,11 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] [[package]] name = "rich" -version = "12.6.0" +version = "13.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" category = "main" optional = false -python-versions = ">=3.6.3,<4.0.0" +python-versions = ">=3.7.0" [package.dependencies] commonmark = ">=0.9.0,<0.10.0" @@ -2260,7 +2289,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = ">=3.8,<3.11" -content-hash = "8d8016030a59d98650765afacecbd23fae40ab7039b7de40b5e41dec9b5fd671" +content-hash = "9e27f8ca8e2dcc81b37b7fbeda3cd6bc049e0ae4c9e2c3a8b01a63298695efd1" [metadata.files] absl-py = [ @@ -2494,6 +2523,59 @@ contourpy = [ {file = "contourpy-1.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9b0e7fe7f949fb719b206548e5cde2518ffb29936afa4303d8a1c4db43dcb675"}, {file = "contourpy-1.0.6.tar.gz", hash = "sha256:6e459ebb8bb5ee4c22c19cc000174f8059981971a33ce11e17dddf6aca97a142"}, ] +coverage = [ + {file = "coverage-7.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c"}, + {file = "coverage-7.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498"}, + {file = "coverage-7.0.1-cp310-cp310-win32.whl", hash = "sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a"}, + {file = "coverage-7.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d"}, + {file = "coverage-7.0.1-cp311-cp311-win32.whl", hash = "sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7"}, + {file = "coverage-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390"}, + {file = "coverage-7.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4"}, + {file = "coverage-7.0.1-cp37-cp37m-win32.whl", hash = "sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c"}, + {file = "coverage-7.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab"}, + {file = "coverage-7.0.1-cp38-cp38-win32.whl", hash = "sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62"}, + {file = "coverage-7.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"}, + {file = "coverage-7.0.1-cp39-cp39-win32.whl", hash = "sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c"}, + {file = "coverage-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8"}, + {file = "coverage-7.0.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89"}, + {file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"}, +] cycler = [ {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, @@ -3427,6 +3509,10 @@ pytest = [ {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, ] +pytest-cov = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, @@ -3578,8 +3664,8 @@ requests-oauthlib = [ {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, ] rich = [ - {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"}, - {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, + {file = "rich-13.0.0-py3-none-any.whl", hash = "sha256:12b1d77ee7edf251b741531323f0d990f5f570a4e7c054d0bfb59fb7981ad977"}, + {file = "rich-13.0.0.tar.gz", hash = "sha256:3aa9eba7219b8c575c6494446a59f702552efe1aa261e7eeb95548fa586e1950"}, ] rsa = [ {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, diff --git a/pyproject.toml b/pyproject.toml index f049c3f..a45ecc1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ mkdocs-material = ">=8.5.10" mkdocs-jupyter = {version = ">=0.22.0", python = ">=3.7.1,<3.11"} clu = ">=0.0.8" pre-commit = ">=2.21.0" +pytest-cov = ">=4.0.0"