diff --git a/.github/workflows/pypi_release.yaml b/.github/workflows/pypi_release.yaml index 6f2cc49..3c175af 100644 --- a/.github/workflows/pypi_release.yaml +++ b/.github/workflows/pypi_release.yaml @@ -18,3 +18,4 @@ jobs: uses: JRubics/poetry-publish@v2.0 with: pypi_token: ${{ secrets.PYPI_TOKEN }} + poetry_install_options: "--without dev" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..a066362 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,33 @@ +name: Tests + +on: + pull_request: + types: [opened, reopened, ready_for_review, review_requested] + +jobs: + run-pytest: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Install dependencies + run: poetry install --no-interaction --with dev + + - name: Run tests + run: poetry run pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=tests/ > pytest-coverage.txt + + - name: Pytest coverage comment + uses: MishaKav/pytest-coverage-comment@main + with: + pytest-coverage-path: ./pytest-coverage.txt + junitxml-path: ./pytest.xml diff --git a/mqboost/constraints.py b/mqboost/constraints.py index ba4e68a..b9ef056 100644 --- a/mqboost/constraints.py +++ b/mqboost/constraints.py @@ -1,13 +1,6 @@ import pandas as pd -from mqboost.base import ( - FUNC_TYPE, - ModelName, - MQStr, - ParamsLike, - TypeName, - ValidationException, -) +from mqboost.base import FUNC_TYPE, ModelName, MQStr, ParamsLike, TypeName def set_monotone_constraints( @@ -27,10 +20,6 @@ def set_monotone_constraints( ParamsLike """ constraints_fucs = FUNC_TYPE.get(model_name).get(TypeName.constraints_type) - if MQStr.obj.value in params: - raise ValidationException( - "The parameter named 'objective' must be excluded in params" - ) _params = params.copy() if MQStr.mono.value in _params: _monotone_constraints = list(_params[MQStr.mono.value]) diff --git a/mqboost/dataset.py b/mqboost/dataset.py index 7ff34a3..4e85a21 100644 --- a/mqboost/dataset.py +++ b/mqboost/dataset.py @@ -74,11 +74,6 @@ def predict_dtype(self) -> Callable: """Get the data type function for prediction data.""" return self._predict_dtype - @property - def model(self) -> ModelName: - """Get the model type.""" - return self._model - @property def columns(self) -> pd.Index: """Get the column names of the input features.""" diff --git a/mqboost/objective.py b/mqboost/objective.py index 72fe4f4..2dfb8ab 100644 --- a/mqboost/objective.py +++ b/mqboost/objective.py @@ -4,7 +4,7 @@ import numpy as np from mqboost.base import DtrainLike, ModelName, ObjectiveName -from mqboost.utils import delta_validate +from mqboost.utils import delta_validate, epsilon_validate CHECK_LOSS: str = "check_loss" @@ -26,19 +26,13 @@ def _hess_rho(error: np.ndarray, alpha: float) -> np.ndarray: # Huber loss -def _error_delta_compare( - error: np.ndarray, delta: float -) -> tuple[np.ndarray, np.ndarray]: - """Rerutn two boolean arrays indicating where the errors are smaller or larger than delta.""" - _abs_error = np.abs(error) - return (_abs_error <= delta).astype(int), (_abs_error > delta).astype(int) - - def _grad_huber(error: np.ndarray, alpha: float, delta: float) -> np.ndarray: """Compute the gradient of the huber loss function.""" - _smaller_delta, _bigger_delta = _error_delta_compare(error=error, delta=delta) - _grad = _grad_rho(error=error, alpha=alpha) + _abs_error = np.abs(error) + _smaller_delta = (_abs_error <= delta).astype(int) + _bigger_delta = (_abs_error > delta).astype(int) _r = _rho(error=error, alpha=alpha) + _grad = _grad_rho(error=error, alpha=alpha) return _r * _smaller_delta + _grad * _bigger_delta @@ -174,9 +168,10 @@ def __init__( if objective == ObjectiveName.check: self._fobj = partial(check_loss_grad_hess, alphas=alphas) elif objective == ObjectiveName.huber: - _delta = delta_validate(delta=delta) - self._fobj = partial(huber_loss_grad_hess, alphas=alphas, delta=_delta) + delta_validate(delta=delta) + self._fobj = partial(huber_loss_grad_hess, alphas=alphas, delta=delta) elif objective == ObjectiveName.approx: + epsilon_validate(epsilon=epsilon) self._fobj = partial(approx_loss_grad_hess, alphas=alphas, epsilon=epsilon) if model == ModelName.lightgbm: diff --git a/mqboost/optimize.py b/mqboost/optimize.py index 06552e4..e3882d4 100644 --- a/mqboost/optimize.py +++ b/mqboost/optimize.py @@ -19,7 +19,7 @@ from mqboost.constraints import set_monotone_constraints from mqboost.dataset import MQDataset from mqboost.objective import MQObjective -from mqboost.utils import delta_validate +from mqboost.utils import delta_validate, epsilon_validate, params_validate __all__ = ["MQOptimizer"] @@ -93,9 +93,12 @@ def __init__( epsilon: float = 1e-5, ) -> None: """Initialize the MQOptimizer.""" + delta_validate(delta=delta) + epsilon_validate(epsilon=epsilon) + self._model = ModelName.get(model) self._objective = ObjectiveName.get(objective) - self._delta = delta_validate(delta) + self._delta = delta self._epsilon = epsilon self._get_params = _GET_PARAMS_FUNC.get(self._model) @@ -179,6 +182,7 @@ def __optuna_objective( ) -> float: """Objective function for Optuna to minimize.""" params = get_params_func(trial=trial) + params_validate(params=params) params = set_monotone_constraints( params=params, columns=self._dataset.columns, diff --git a/mqboost/regressor.py b/mqboost/regressor.py index 16d9d58..26d293b 100644 --- a/mqboost/regressor.py +++ b/mqboost/regressor.py @@ -6,6 +6,7 @@ from mqboost.constraints import set_monotone_constraints from mqboost.dataset import MQDataset from mqboost.objective import MQObjective +from mqboost.utils import params_validate __all__ = ["MQRegressor"] @@ -46,6 +47,7 @@ def __init__( epsilon: float = 1e-5, ) -> None: """Initialize the MQRegressor.""" + params_validate(params=params) self._params = params self._model = ModelName.get(model) self._objective = ObjectiveName.get(objective) diff --git a/mqboost/utils.py b/mqboost/utils.py index 8c9f592..9a9fa2e 100644 --- a/mqboost/utils.py +++ b/mqboost/utils.py @@ -4,7 +4,14 @@ import numpy as np import pandas as pd -from mqboost.base import AlphaLike, ValidationException, XdataLike, YdataLike +from mqboost.base import ( + AlphaLike, + MQStr, + ParamsLike, + ValidationException, + XdataLike, + YdataLike, +) def alpha_validate( @@ -49,7 +56,7 @@ def prepare_x( _alpha_repeat_count_list = [list(repeat(alpha, len(x))) for alpha in alphas] _alpha_repeat_list = list(chain.from_iterable(_alpha_repeat_count_list)) - _repeated_x = pd.concat([x] * len(alphas), axis=0) + _repeated_x = pd.concat([x] * len(alphas), axis=0).reset_index(drop=True) _repeated_x = _repeated_x.assign( _tau=_alpha_repeat_list, ) @@ -64,7 +71,7 @@ def prepare_y( return np.concatenate(list(repeat(y, len(alphas)))) -def delta_validate(delta: float) -> float: +def delta_validate(delta: float) -> None: """Validates the delta parameter ensuring it is a positive float and less than or equal to 0.05.""" _delta_upper_bound: float = 0.05 @@ -77,4 +84,18 @@ def delta_validate(delta: float) -> float: if delta > _delta_upper_bound: warnings.warn("Delta should be 0.05 or less.") - return delta + +def epsilon_validate(epsilon: float) -> None: + if not isinstance(epsilon, float): + raise ValidationException("Epsilon is not float type") + + if epsilon <= 0: + raise ValidationException("Epsilon must be positive") + + +def params_validate(params: ParamsLike) -> None: + """Validates the model parameter ensuring its key dosen't contain 'objective'.""" + if MQStr.obj.value in params: + raise ValidationException( + "The parameter named 'objective' must be excluded in params" + ) diff --git a/poetry.lock b/poetry.lock index 4d32de4..c86f7b4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -19,35 +19,6 @@ typing-extensions = ">=4" [package.extras] tz = ["backports.zoneinfo"] -[[package]] -name = "appnope" -version = "0.1.4" -description = "Disable App Nap on macOS >= 10.9" -optional = false -python-versions = ">=3.6" -files = [ - {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, - {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, -] - -[[package]] -name = "asttokens" -version = "2.4.1" -description = "Annotate AST trees with source code positions" -optional = false -python-versions = "*" -files = [ - {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, - {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, -] - -[package.dependencies] -six = ">=1.12.0" - -[package.extras] -astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] -test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] - [[package]] name = "black" version = "24.4.2" @@ -94,70 +65,6 @@ d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] -[[package]] -name = "cffi" -version = "1.16.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, -] - -[package.dependencies] -pycparser = "*" - [[package]] name = "cfgv" version = "3.4.0" @@ -212,63 +119,91 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} development = ["black", "flake8", "mypy", "pytest", "types-colorama"] [[package]] -name = "comm" -version = "0.2.2" -description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +name = "coverage" +version = "7.6.1" +description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, - {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.dependencies] -traitlets = ">=4" +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -test = ["pytest"] - -[[package]] -name = "debugpy" -version = "1.8.2" -description = "An implementation of the Debug Adapter Protocol for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "debugpy-1.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7ee2e1afbf44b138c005e4380097d92532e1001580853a7cb40ed84e0ef1c3d2"}, - {file = "debugpy-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f8c3f7c53130a070f0fc845a0f2cee8ed88d220d6b04595897b66605df1edd6"}, - {file = "debugpy-1.8.2-cp310-cp310-win32.whl", hash = "sha256:f179af1e1bd4c88b0b9f0fa153569b24f6b6f3de33f94703336363ae62f4bf47"}, - {file = "debugpy-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:0600faef1d0b8d0e85c816b8bb0cb90ed94fc611f308d5fde28cb8b3d2ff0fe3"}, - {file = "debugpy-1.8.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8a13417ccd5978a642e91fb79b871baded925d4fadd4dfafec1928196292aa0a"}, - {file = "debugpy-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acdf39855f65c48ac9667b2801234fc64d46778021efac2de7e50907ab90c634"}, - {file = "debugpy-1.8.2-cp311-cp311-win32.whl", hash = "sha256:2cbd4d9a2fc5e7f583ff9bf11f3b7d78dfda8401e8bb6856ad1ed190be4281ad"}, - {file = "debugpy-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:d3408fddd76414034c02880e891ea434e9a9cf3a69842098ef92f6e809d09afa"}, - {file = "debugpy-1.8.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:5d3ccd39e4021f2eb86b8d748a96c766058b39443c1f18b2dc52c10ac2757835"}, - {file = "debugpy-1.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62658aefe289598680193ff655ff3940e2a601765259b123dc7f89c0239b8cd3"}, - {file = "debugpy-1.8.2-cp312-cp312-win32.whl", hash = "sha256:bd11fe35d6fd3431f1546d94121322c0ac572e1bfb1f6be0e9b8655fb4ea941e"}, - {file = "debugpy-1.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:15bc2f4b0f5e99bf86c162c91a74c0631dbd9cef3c6a1d1329c946586255e859"}, - {file = "debugpy-1.8.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:5a019d4574afedc6ead1daa22736c530712465c0c4cd44f820d803d937531b2d"}, - {file = "debugpy-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40f062d6877d2e45b112c0bbade9a17aac507445fd638922b1a5434df34aed02"}, - {file = "debugpy-1.8.2-cp38-cp38-win32.whl", hash = "sha256:c78ba1680f1015c0ca7115671fe347b28b446081dada3fedf54138f44e4ba031"}, - {file = "debugpy-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf327316ae0c0e7dd81eb92d24ba8b5e88bb4d1b585b5c0d32929274a66a5210"}, - {file = "debugpy-1.8.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:1523bc551e28e15147815d1397afc150ac99dbd3a8e64641d53425dba57b0ff9"}, - {file = "debugpy-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e24ccb0cd6f8bfaec68d577cb49e9c680621c336f347479b3fce060ba7c09ec1"}, - {file = "debugpy-1.8.2-cp39-cp39-win32.whl", hash = "sha256:7f8d57a98c5a486c5c7824bc0b9f2f11189d08d73635c326abef268f83950326"}, - {file = "debugpy-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:16c8dcab02617b75697a0a925a62943e26a0330da076e2a10437edd9f0bf3755"}, - {file = "debugpy-1.8.2-py2.py3-none-any.whl", hash = "sha256:16e16df3a98a35c63c3ab1e4d19be4cbc7fdda92d9ddc059294f18910928e0ca"}, - {file = "debugpy-1.8.2.zip", hash = "sha256:95378ed08ed2089221896b9b3a8d021e642c24edc8fef20e5d4342ca8be65c00"}, -] - -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -optional = false -python-versions = ">=3.5" -files = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] +toml = ["tomli"] [[package]] name = "distlib" @@ -295,20 +230,6 @@ files = [ [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "executing" -version = "2.0.1" -description = "Get the currently executing AST node of a frame, and other information" -optional = false -python-versions = ">=3.5" -files = [ - {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, - {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, -] - -[package.extras] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] - [[package]] name = "filelock" version = "3.15.4" @@ -411,94 +332,16 @@ files = [ license = ["ukkonen"] [[package]] -name = "importlib-metadata" -version = "8.2.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, - {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] - -[[package]] -name = "ipykernel" -version = "6.29.5" -description = "IPython Kernel for Jupyter" -optional = false -python-versions = ">=3.8" -files = [ - {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, - {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, -] - -[package.dependencies] -appnope = {version = "*", markers = "platform_system == \"Darwin\""} -comm = ">=0.1.1" -debugpy = ">=1.6.5" -ipython = ">=7.23.1" -jupyter-client = ">=6.1.12" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" -matplotlib-inline = ">=0.1" -nest-asyncio = "*" -packaging = "*" -psutil = "*" -pyzmq = ">=24" -tornado = ">=6.1" -traitlets = ">=5.4.0" - -[package.extras] -cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] -pyqt5 = ["pyqt5"] -pyside6 = ["pyside6"] -test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] - -[[package]] -name = "ipython" -version = "8.18.1" -description = "IPython: Productive Interactive Computing" +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.9" +python-versions = ">=3.7" files = [ - {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, - {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -prompt-toolkit = ">=3.0.41,<3.1.0" -pygments = ">=2.4.0" -stack-data = "*" -traitlets = ">=5" -typing-extensions = {version = "*", markers = "python_version < \"3.10\""} - -[package.extras] -all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] -black = ["black"] -doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] -kernel = ["ipykernel"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] - [[package]] name = "isort" version = "5.13.2" @@ -513,25 +356,6 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] -[[package]] -name = "jedi" -version = "0.19.1" -description = "An autocompletion tool for Python that can be used for text editors." -optional = false -python-versions = ">=3.6" -files = [ - {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, - {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, -] - -[package.dependencies] -parso = ">=0.8.3,<0.9.0" - -[package.extras] -docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] - [[package]] name = "joblib" version = "1.4.2" @@ -543,49 +367,6 @@ files = [ {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, ] -[[package]] -name = "jupyter-client" -version = "8.6.2" -description = "Jupyter protocol implementation and client libraries" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f"}, - {file = "jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" -python-dateutil = ">=2.8.2" -pyzmq = ">=23.0" -tornado = ">=6.2" -traitlets = ">=5.3" - -[package.extras] -docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] -test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] - -[[package]] -name = "jupyter-core" -version = "5.7.2" -description = "Jupyter core package. A base package on which Jupyter projects rely." -optional = false -python-versions = ">=3.8" -files = [ - {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, - {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, -] - -[package.dependencies] -platformdirs = ">=2.5" -pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} -traitlets = ">=5.3" - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] -test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] - [[package]] name = "lightgbm" version = "4.4.0" @@ -699,20 +480,6 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] -[[package]] -name = "matplotlib-inline" -version = "0.1.7" -description = "Inline Matplotlib backend for Jupyter" -optional = false -python-versions = ">=3.8" -files = [ - {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, - {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, -] - -[package.dependencies] -traitlets = "*" - [[package]] name = "mypy-extensions" version = "1.0.0" @@ -724,17 +491,6 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -[[package]] -name = "nest-asyncio" -version = "1.6.0" -description = "Patch asyncio to allow nested event loops" -optional = false -python-versions = ">=3.5" -files = [ - {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, - {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, -] - [[package]] name = "nodeenv" version = "1.9.1" @@ -921,21 +677,6 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] -[[package]] -name = "parso" -version = "0.8.4" -description = "A Python Parser" -optional = false -python-versions = ">=3.6" -files = [ - {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, - {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, -] - -[package.extras] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["docopt", "pytest"] - [[package]] name = "pathspec" version = "0.12.1" @@ -947,20 +688,6 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -[[package]] -name = "pexpect" -version = "4.9.0" -description = "Pexpect allows easy control of interactive console applications." -optional = false -python-versions = "*" -files = [ - {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, - {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, -] - -[package.dependencies] -ptyprocess = ">=0.5" - [[package]] name = "platformdirs" version = "4.2.2" @@ -978,19 +705,19 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest- type = ["mypy (>=1.8)"] [[package]] -name = "plotly" -version = "5.22.0" -description = "An open-source, interactive data visualization library for Python" +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "plotly-5.22.0-py3-none-any.whl", hash = "sha256:68fc1901f098daeb233cc3dd44ec9dc31fb3ca4f4e53189344199c43496ed006"}, - {file = "plotly-5.22.0.tar.gz", hash = "sha256:859fdadbd86b5770ae2466e542b761b247d1c6b49daed765b95bb8c7063e7469"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] -[package.dependencies] -packaging = "*" -tenacity = ">=6.2.0" +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" @@ -1011,97 +738,44 @@ pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] -name = "prompt-toolkit" -version = "3.0.47" -description = "Library for building powerful interactive command lines in Python" +name = "pytest" +version = "8.3.2" +description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8" files = [ - {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, - {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] -wcwidth = "*" - -[[package]] -name = "psutil" -version = "6.0.0" -description = "Cross-platform lib for process and system monitoring in Python." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, - {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, - {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, - {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, - {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, - {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, - {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, - {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, - {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, - {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, - {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, - {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, - {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, - {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, -] - -[package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -optional = false -python-versions = "*" -files = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -description = "Safely evaluate AST nodes without side effects" -optional = false -python-versions = "*" -files = [ - {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, - {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, -] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -tests = ["pytest"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, ] -[[package]] -name = "pygments" -version = "2.18.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, -] +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" [package.extras] -windows-terminal = ["colorama (>=0.4.6)"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "python-dateutil" @@ -1128,29 +802,6 @@ files = [ {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] -[[package]] -name = "pywin32" -version = "306" -description = "Python for Window Extensions" -optional = false -python-versions = "*" -files = [ - {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, - {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, - {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, - {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, - {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, - {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, - {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, - {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, - {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, - {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, - {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, - {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, - {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, - {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, -] - [[package]] name = "pyyaml" version = "6.0.1" @@ -1211,106 +862,6 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] -[[package]] -name = "pyzmq" -version = "26.0.3" -description = "Python bindings for 0MQ" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625"}, - {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8"}, - {file = "pyzmq-26.0.3-cp310-cp310-win32.whl", hash = "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537"}, - {file = "pyzmq-26.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47"}, - {file = "pyzmq-26.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7"}, - {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32"}, - {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83"}, - {file = "pyzmq-26.0.3-cp311-cp311-win32.whl", hash = "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3"}, - {file = "pyzmq-26.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500"}, - {file = "pyzmq-26.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94"}, - {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753"}, - {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798"}, - {file = "pyzmq-26.0.3-cp312-cp312-win32.whl", hash = "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0"}, - {file = "pyzmq-26.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf"}, - {file = "pyzmq-26.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b"}, - {file = "pyzmq-26.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5"}, - {file = "pyzmq-26.0.3-cp37-cp37m-win32.whl", hash = "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf"}, - {file = "pyzmq-26.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a"}, - {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18"}, - {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97"}, - {file = "pyzmq-26.0.3-cp38-cp38-win32.whl", hash = "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"}, - {file = "pyzmq-26.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972"}, - {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606"}, - {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920"}, - {file = "pyzmq-26.0.3-cp39-cp39-win32.whl", hash = "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879"}, - {file = "pyzmq-26.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2"}, - {file = "pyzmq-26.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad"}, - {file = "pyzmq-26.0.3.tar.gz", hash = "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a"}, -] - -[package.dependencies] -cffi = {version = "*", markers = "implementation_name == \"pypy\""} - [[package]] name = "scikit-learn" version = "1.5.1" @@ -1496,40 +1047,6 @@ postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] sqlcipher = ["sqlcipher3_binary"] -[[package]] -name = "stack-data" -version = "0.6.3" -description = "Extract data from python stack frames and tracebacks for informative displays" -optional = false -python-versions = "*" -files = [ - {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, - {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, -] - -[package.dependencies] -asttokens = ">=2.1.0" -executing = ">=1.2.0" -pure-eval = "*" - -[package.extras] -tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] - -[[package]] -name = "tenacity" -version = "8.5.0" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, - {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, -] - -[package.extras] -doc = ["reno", "sphinx"] -test = ["pytest", "tornado (>=4.5)", "typeguard"] - [[package]] name = "threadpoolctl" version = "3.5.0" @@ -1552,26 +1069,6 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[[package]] -name = "tornado" -version = "6.4.1" -description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -optional = false -python-versions = ">=3.8" -files = [ - {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, - {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, - {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, - {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, - {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, - {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, - {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, - {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, - {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, - {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, - {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, -] - [[package]] name = "tqdm" version = "4.66.4" @@ -1592,21 +1089,6 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] -[[package]] -name = "traitlets" -version = "5.14.3" -description = "Traitlets Python configuration system" -optional = false -python-versions = ">=3.8" -files = [ - {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, - {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, -] - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] - [[package]] name = "typing-extensions" version = "4.12.2" @@ -1649,17 +1131,6 @@ platformdirs = ">=3.9.1,<5" docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] -[[package]] -name = "wcwidth" -version = "0.2.13" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = "*" -files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, -] - [[package]] name = "xgboost" version = "2.1.0" @@ -1690,22 +1161,7 @@ plotting = ["graphviz", "matplotlib"] pyspark = ["cloudpickle", "pyspark", "scikit-learn"] scikit-learn = ["scikit-learn"] -[[package]] -name = "zipp" -version = "3.19.2" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, -] - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] - [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "0001c60594d7516c9e84638144859a56ecadd977abcb99a52d672e1e32a154d0" +content-hash = "1dfeb7a6167b3f8c016453de2427c13baa8b13c53cf3407c87fbe809078a48b0" diff --git a/pyproject.toml b/pyproject.toml index c604330..a5ec010 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,11 +18,11 @@ scikit-learn = "^1.5.1" [tool.poetry.group.dev.dependencies] -plotly = "^5.22.0" pre-commit = "^3.7.1" isort = "^5.13.2" black = "^24.4.2" -ipykernel = "^6.29.5" +pytest = "^8.3.2" +pytest-cov = "^5.0.0" [build-system] requires = ["poetry-core"] diff --git a/tests/test_base.py b/tests/test_base.py new file mode 100644 index 0000000..89f7051 --- /dev/null +++ b/tests/test_base.py @@ -0,0 +1,75 @@ +import lightgbm as lgb +import numpy as np +import pandas as pd +import pytest +import xgboost as xgb + +from mqboost.base import ( + FUNC_TYPE, + FittingException, + ModelName, + ObjectiveName, + TypeName, + ValidationException, + _lgb_predict_dtype, +) + + +# Test Enum behavior +def test_model_name_enum(): + assert ModelName.get("lightgbm") == ModelName.lightgbm + assert ModelName.get("xgboost") == ModelName.xgboost + + with pytest.raises(ValueError): + ModelName.get("invalid_model") + + +def test_objective_name_enum(): + assert ObjectiveName.get("check") == ObjectiveName.check + assert ObjectiveName.get("huber") == ObjectiveName.huber + assert ObjectiveName.get("approx") == ObjectiveName.approx + + with pytest.raises(ValueError): + ObjectiveName.get("invalid_objective") + + +# Test FUNC_TYPE +def test_func_type_for_lgb(): + assert FUNC_TYPE[ModelName.lightgbm][TypeName.train_dtype] == lgb.Dataset + assert isinstance( + FUNC_TYPE[ModelName.lightgbm][TypeName.predict_dtype](pd.DataFrame([1, 2, 3])), + pd.DataFrame, + ) + assert isinstance( + FUNC_TYPE[ModelName.lightgbm][TypeName.predict_dtype](pd.Series([1, 2, 3])), + pd.Series, + ) + assert isinstance( + FUNC_TYPE[ModelName.lightgbm][TypeName.predict_dtype](np.array([1, 2, 3])), + np.ndarray, + ) + assert FUNC_TYPE[ModelName.lightgbm][TypeName.constraints_type] == list + + +def test_func_type_for_xgb(): + assert FUNC_TYPE[ModelName.xgboost][TypeName.train_dtype] == xgb.DMatrix + assert FUNC_TYPE[ModelName.xgboost][TypeName.predict_dtype] == xgb.DMatrix + assert FUNC_TYPE[ModelName.xgboost][TypeName.constraints_type] == tuple + + +# Test _lgb_predict_dtype +def test_lgb_predict_dtype(): + data = pd.DataFrame([1, 2, 3]) + assert _lgb_predict_dtype(data) is data + + array_data = np.array([1, 2, 3]) + assert _lgb_predict_dtype(array_data) is array_data + + +# Test custom exceptions +def test_custom_exceptions(): + with pytest.raises(FittingException): + raise FittingException("Fitting failed") + + with pytest.raises(ValidationException): + raise ValidationException("Validation failed") diff --git a/tests/test_constraints.py b/tests/test_constraints.py new file mode 100644 index 0000000..5daaa83 --- /dev/null +++ b/tests/test_constraints.py @@ -0,0 +1,64 @@ +import pandas as pd + +from mqboost.base import ModelName, MQStr +from mqboost.constraints import set_monotone_constraints + + +# Test function for setting monotone constraints +def test_set_monotone_constraints_with_existing_constraints_lgb(): + params = { + MQStr.mono.value: [1, -1], + } + columns = pd.Index(["feature_1", "feature_2", "_tau"]) + model_name = ModelName.lightgbm + updated_params = set_monotone_constraints(params, columns, model_name) + expected_constraints = [1, -1, 1] + assert updated_params[MQStr.mono.value] == expected_constraints + + +def test_set_monotone_constraints_without_existing_constraints_lgb(): + params = {} + columns = pd.Index(["feature_1", "feature_2", "_tau"]) + model_name = ModelName.lightgbm + updated_params = set_monotone_constraints(params, columns, model_name) + expected_constraints = [0, 0, 1] + assert updated_params[MQStr.mono.value] == expected_constraints + + +def test_set_monotone_constraints_with_existing_constraints_xgb(): + params = { + MQStr.mono.value: [1, -1], + } + columns = pd.Index(["feature_1", "feature_2", "_tau"]) + model_name = ModelName.xgboost + updated_params = set_monotone_constraints(params, columns, model_name) + expected_constraints = (1, -1, 1) + assert updated_params[MQStr.mono.value] == expected_constraints + + +def test_set_monotone_constraints_without_existing_constraints_xgb(): + params = {} + columns = pd.Index(["feature_1", "feature_2", "_tau"]) + model_name = ModelName.xgboost + updated_params = set_monotone_constraints(params, columns, model_name) + expected_constraints = (0, 0, 1) + assert updated_params[MQStr.mono.value] == expected_constraints + + +# Edge case where no constraints or empty columns +def test_set_monotone_constraints_with_empty_columns(): + params = {} + columns = pd.Index([]) + model_name = ModelName.lightgbm + updated_params = set_monotone_constraints(params, columns, model_name) + expected_constraints = [] + assert updated_params[MQStr.mono.value] == expected_constraints + + +def test_set_monotone_constraints_with_empty_params_and_columns(): + params = {} + columns = pd.Index([]) + model_name = ModelName.xgboost + updated_params = set_monotone_constraints(params, columns, model_name) + expected_constraints = () + assert updated_params[MQStr.mono.value] == expected_constraints diff --git a/tests/test_dataset.py b/tests/test_dataset.py new file mode 100644 index 0000000..666e236 --- /dev/null +++ b/tests/test_dataset.py @@ -0,0 +1,123 @@ +import numpy as np +import pandas as pd +import pytest + +from mqboost.base import FittingException, ModelName, ValidationException +from mqboost.dataset import MQDataset + + +def _concat(df: pd.DataFrame, concat_count: int): + return pd.concat([df] * concat_count, axis=0).reset_index(drop=True) + + +# Test for MQDataset initialization +def test_mqdataset_initialization_with_lgb(): + data = pd.DataFrame({"feature_1": [1, 2, 3], "feature_2": [4, 5, 6]}) + label = pd.Series([1, 2, 3]) + alphas = [0.1, 0.2, 0.3] + dataset = MQDataset( + alphas=alphas, data=data, label=label, model=ModelName.lightgbm.value + ) + + assert dataset.nrow == 3 + assert dataset.alphas == alphas + pd.testing.assert_frame_equal( + dataset.data, + _concat(data, 3).assign(_tau=[0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.3, 0.3, 0.3]), + ) + np.testing.assert_array_equal(dataset.label, np.concatenate([label] * len(alphas))) + + +def test_mqdataset_initialization_with_xgb(): + data = pd.DataFrame({"feature_1": [1, 2, 3], "feature_2": [4, 5, 6]}) + label = pd.Series([1, 2, 3]) + alphas = [0.1, 0.2] + dataset = MQDataset( + alphas=alphas, data=data, label=label, model=ModelName.xgboost.value + ) + + assert dataset.nrow == 3 + assert dataset.alphas == alphas + pd.testing.assert_frame_equal( + dataset.data, _concat(data, 2).assign(_tau=[0.1, 0.1, 0.1, 0.2, 0.2, 0.2]) + ) + np.testing.assert_array_equal(dataset.label, np.concatenate([label] * len(alphas))) + + +def test_mqdataset_initialization_with_invalid_alpha(): + data = pd.DataFrame({"feature_1": [1, 2, 3], "feature_2": [4, 5, 6]}) + + with pytest.raises(ValidationException, match="Alpha is not ascending order"): + MQDataset(alphas=[0.3, 0.2], data=data) + + +def test_mqdataset_initialization_without_label(): + data = pd.DataFrame({"feature_1": [1, 2, 3], "feature_2": [4, 5, 6]}) + alphas = [0.1, 0.2] + dataset = MQDataset(alphas=alphas, data=data, model=ModelName.lightgbm.value) + + assert dataset.nrow == 3 + assert dataset.alphas == alphas + + pd.testing.assert_frame_equal( + dataset.data, _concat(data, 2).assign(_tau=[0.1, 0.1, 0.1, 0.2, 0.2, 0.2]) + ) + + # Ensure label is not available + with pytest.raises( + FittingException, match="Fitting is impossible since label is None" + ): + dataset.label + + +# Test properties +def test_mqdataset_train_predict_dtype(): + data = pd.DataFrame({"feature_1": [1, 2, 3], "feature_2": [4, 5, 6]}) + alphas = [0.1, 0.2] + dataset = MQDataset(alphas=alphas, data=data, model=ModelName.lightgbm.value) + assert dataset.train_dtype == dataset._train_dtype + assert dataset.predict_dtype == dataset._predict_dtype + + dataset = MQDataset(alphas=alphas, data=data, model=ModelName.xgboost.value) + assert dataset.train_dtype == dataset._train_dtype + assert dataset.predict_dtype == dataset._predict_dtype + + +def test_mqdataset_columns_property(): + data = pd.DataFrame({"feature_1": [1, 2, 3], "feature_2": [4, 5, 6]}) + alphas = [0.1, 0.2] + + dataset = MQDataset(alphas=alphas, data=data, model=ModelName.lightgbm.value) + assert list(dataset.columns) == [ + "feature_1", + "feature_2", + "_tau", + ] + + +def test_mqdataset_dtype_lgb(): + data = pd.DataFrame({"feature_1": [1, 2, 3], "feature_2": [4, 5, 6]}) + label = pd.Series([1, 2, 3]) + alphas = [0.1, 0.2] + dataset = MQDataset( + alphas=alphas, data=data, label=label, model=ModelName.lightgbm.value + ) + + dtrain = dataset.dtrain + dpredict = dataset.dpredict + assert isinstance(dtrain, dataset.train_dtype) + assert isinstance(dpredict, pd.DataFrame) + + +def test_mqdataset_dtype_xgb(): + data = pd.DataFrame({"feature_1": [1, 2, 3], "feature_2": [4, 5, 6]}) + label = pd.Series([1, 2, 3]) + alphas = [0.1, 0.2] + dataset = MQDataset( + alphas=alphas, data=data, label=label, model=ModelName.xgboost.value + ) + + dtrain = dataset.dtrain + dpredict = dataset.dpredict + assert isinstance(dtrain, dataset.train_dtype) + assert isinstance(dpredict, dataset.predict_dtype) diff --git a/tests/test_objective.py b/tests/test_objective.py new file mode 100644 index 0000000..cbe5e80 --- /dev/null +++ b/tests/test_objective.py @@ -0,0 +1,200 @@ +import numpy as np +import pytest + +from mqboost.base import ModelName, ObjectiveName, ValidationException +from mqboost.objective import ( + MQObjective, + _eval_check_loss, + _lgb_eval_loss, + _xgb_eval_loss, + approx_loss_grad_hess, + check_loss_grad_hess, + huber_loss_grad_hess, +) + + +@pytest.fixture +def dummy_data(): + class DummyDtrain: + def __init__(self, label): + self.label = label + + def get_label(self): + return self.label + + return DummyDtrain + + +alphas = [0.1, 0.5, 0.9] +y_pred = np.array([1.0, 1.5, 2.0, 2.5, 3.0] * 3) +y_true = np.array([1.01, 1.51, 1.98, 2.53, 3.05] * 3) + + +# Test MQObjective Initialization +def test_mqobjective_check_loss_initialization(): + """Test MQObjective initialization with check loss.""" + + mq_objective = MQObjective( + alphas=alphas, + objective=ObjectiveName.check, + model=ModelName.xgboost, + delta=0.0, + epsilon=0.0, + ) + assert mq_objective.fobj is not None + assert mq_objective.feval is not None + assert callable(mq_objective.fobj) + assert callable(mq_objective.feval) + + +def test_mqobjective_huber_loss_initialization(): + """Test MQObjective initialization with huber loss.""" + delta = 0.1 + mq_objective = MQObjective( + alphas=alphas, + objective=ObjectiveName.huber, + model=ModelName.lightgbm, + delta=delta, + epsilon=0.0, + ) + assert mq_objective.fobj is not None + assert callable(mq_objective.fobj) + + +def test_mqobjective_approx_loss_initialization(): + """Test MQObjective initialization with approx loss.""" + epsilon = 0.1 + mq_objective = MQObjective( + alphas=alphas, + objective=ObjectiveName.approx, + model=ModelName.xgboost, + delta=0.0, + epsilon=epsilon, + ) + assert mq_objective.fobj is not None + assert callable(mq_objective.fobj) + + +# Test loss functions: check, huber, approx +def test_check_loss_grad_hess(dummy_data): + """Test check loss gradient and Hessian calculation.""" + dtrain = dummy_data(y_true) + grads, hess = check_loss_grad_hess(y_pred=y_pred, dtrain=dtrain, alphas=alphas) + # fmt: off + expected_grads = [-0.1, -0.1, 0.9, -0.1, -0.1, -0.5, -0.5, 0.5, -0.5, -0.5, -0.9, -0.9, 0.1, -0.9, -0.9] + # fmt: on + np.testing.assert_almost_equal(grads, np.array(expected_grads)) + assert grads.shape == hess.shape + assert len(grads) == len(y_pred) + + +# fmt: off +@pytest.mark.parametrize( + "delta, expected_grads", + [ + (0.01, [-0.1, -0.1, 0.9, -0.1, -0.1, -0.5, -0.5, 0.5, -0.5, -0.5, -0.9, -0.9, 0.1, -0.9, -0.9]), + (0.02, [0.001, 0.001, 0.9, -0.1, -0.1, 0.005, 0.005, 0.5, -0.5, -0.5, 0.009, 0.009, 0.1, -0.9, -0.9]), + (0.05, [0.001, 0.001, 0.018, 0.003, 0.005, 0.005, 0.005, 0.01, 0.015, 0.025, 0.009, 0.009, 0.002, 0.027, 0.045]), + ], +) +# fmt: on +def test_huber_loss_grad_hess(dummy_data, delta, expected_grads): + """Test huber loss gradient and Hessian calculation with multiple datasets and deltas.""" + dtrain = dummy_data(y_true) + grads, hess = huber_loss_grad_hess( + y_pred=y_pred, dtrain=dtrain, alphas=alphas, delta=delta + ) + + np.testing.assert_almost_equal(grads, np.array(expected_grads)) + assert grads.shape == hess.shape + assert len(grads) == len(y_pred) + + +# fmt: off +@pytest.mark.parametrize( + "epsilon, expected_grads, expected_hess", + [ + ( + 0.01, + [0.15, 0.15, 0.7333, 0.025, -0.0167, -0.25, -0.25, 0.3333, -0.375, -0.4167, -0.65, -0.65, -0.0667, -0.775, -0.8167], + [25.0, 25.0, 16.6666, 12.5, 8.3333, 25.0, 25.0, 16.6666, 12.5, 8.33333333, 25.0, 25.0, 16.6666, 12.5, 8.33333333], + ), + ( + 0.005, + [0.0667, 0.0667, 0.8, -0.02857, -0.0545, -0.3333, -0.3333, 0.4, -0.4286, -0.4545, -0.7333, -0.73333, 0.0, -0.8286, -0.8545], + [33.3333, 33.3333, 20.0, 14.2857, 9.0909, 33.3333, 33.3333, 20.0, 14.2857, 9.0909, 33.3333, 33.3333, 20.0, 14.2857, 9.0909], + ), + ( + 0.001, + [-0.0545, -0.0545, 0.8762, -0.0838, -0.0902, -0.4545, -0.4545, 0.4762, -0.4839, -0.4902, -0.8545, -0.8545, 0.0762, -0.8839, -0.8902], + [45.4545, 45.4545, 23.8095, 16.1290, 9.8039, 45.4545, 45.4545, 23.8095, 16.1290, 9.8039, 45.4545, 45.4545, 23.8095, 16.1290, 9.8039], + ), + ], +) +# fmt: on +def test_approx_loss_grad_hess(dummy_data, epsilon, expected_grads, expected_hess): + """Test approx loss gradient and Hessian calculation.""" + dtrain = dummy_data(y_true) + grads, hess = approx_loss_grad_hess( + y_pred=y_pred, dtrain=dtrain, alphas=alphas, epsilon=epsilon + ) + np.testing.assert_almost_equal(grads, np.array(expected_grads), decimal=4) + np.testing.assert_almost_equal(hess, np.array(expected_hess), decimal=4) + assert grads.shape == hess.shape + assert len(grads) == len(y_pred) + + +# Test evaluation functions +def test_eval_check_loss(dummy_data): + """Test evaluation of the check loss.""" + dtrain = dummy_data(y_true) + loss = _eval_check_loss(y_pred=y_pred, dtrain=dtrain, alphas=alphas) + np.testing.assert_almost_equal(loss, 0.012) + assert isinstance(loss, float) + assert loss > 0 + + +def test_xgb_eval_loss(dummy_data): + """Test XGBoost evaluation function.""" + dtrain = dummy_data(y_true) + metric_name, loss = _xgb_eval_loss(y_pred=y_pred, dtrain=dtrain, alphas=alphas) + assert metric_name == "check_loss" + assert isinstance(loss, float) + + +def test_lgb_eval_loss(dummy_data): + """Test LightGBM evaluation function.""" + dtrain = dummy_data(y_true) + metric_name, loss, higher_better = _lgb_eval_loss( + y_pred=y_pred, dtrain=dtrain, alphas=alphas + ) + assert metric_name == "check_loss" + assert isinstance(loss, float) + assert higher_better is False + + +# Test error handling for invalid parameters +def test_invalid_delta_for_huber(): + """Test that invalid delta for Huber loss raises an exception.""" + alphas = [0.1, 0.5, 0.9] + with pytest.raises(ValidationException): + MQObjective( + alphas=alphas, + objective=ObjectiveName.huber, + model=ModelName.xgboost, + delta=-0.1, # Invalid delta (negative) + epsilon=0.0, + ) + + +def test_invalid_epsilon_for_approx(): + """Test that invalid epsilon for approx loss raises an exception.""" + alphas = [0.1, 0.5, 0.9] + with pytest.raises(ValidationException): + MQObjective( + alphas=alphas, + objective=ObjectiveName.approx, + model=ModelName.xgboost, + delta=0.0, + epsilon=-0.01, # Invalid epsilon (negative) + ) diff --git a/tests/test_optimize.py b/tests/test_optimize.py new file mode 100644 index 0000000..b9b89af --- /dev/null +++ b/tests/test_optimize.py @@ -0,0 +1,208 @@ +import numpy as np +import optuna +import pandas as pd +import pytest + +from mqboost.base import FittingException, ModelName, ObjectiveName, ValidationException +from mqboost.dataset import MQDataset +from mqboost.objective import MQObjective +from mqboost.optimize import MQOptimizer, _lgb_get_params, _xgb_get_params + + +@pytest.fixture +def sample_data(): + """Generates sample data for testing.""" + X = pd.DataFrame(np.random.rand(100, 5), columns=[f"feature_{i}" for i in range(5)]) + y = np.random.rand(100) + alphas = [0.1, 0.5, 0.9] + dataset = MQDataset(alphas=alphas, data=X, label=y, model=ModelName.lightgbm.value) + return dataset + + +@pytest.fixture +def sample_valid_data(): + """Generates sample validation data.""" + X_valid = pd.DataFrame( + np.random.rand(20, 5), columns=[f"feature_{i}" for i in range(5)] + ) + y_valid = np.random.rand(20) + alphas = [0.1, 0.5, 0.9] + valid_set = MQDataset( + alphas=alphas, data=X_valid, label=y_valid, model=ModelName.lightgbm.value + ) + return valid_set + + +# Test MQOptimizer Initialization +def test_mqoptimizer_initialization(): + """Test initialization with valid parameters.""" + optimizer = MQOptimizer( + model=ModelName.lightgbm.value, + objective=ObjectiveName.check.value, + delta=0.01, + epsilon=1e-5, + ) + assert optimizer._model == ModelName.lightgbm + assert optimizer._objective == ObjectiveName.check + assert optimizer._delta == 0.01 + assert optimizer._epsilon == 1e-5 + + +def test_mqoptimizer_invalid_model(): + """Test initialization with an invalid model.""" + with pytest.raises(ValueError): + MQOptimizer(model="invalid_model") + + +def test_mqoptimizer_invalid_objective(): + """Test initialization with an invalid objective.""" + with pytest.raises(ValueError): + MQOptimizer(objective="invalid_objective") + + +def test_mqoptimizer_invalid_delta(): + """Test initialization with invalid delta value.""" + with pytest.raises(ValidationException): + MQOptimizer(delta=-0.01) + + +def test_mqoptimizer_invalid_epsilon(): + """Test initialization with invalid epsilon value.""" + with pytest.raises(ValidationException): + MQOptimizer(epsilon=-1e-5) + + +# Test optimize_params method +def test_optimize_params_with_default_get_params(sample_data): + """Test optimize_params with default get_params function.""" + optimizer = MQOptimizer() + best_params = optimizer.optimize_params(dataset=sample_data, n_trials=1) + assert isinstance(best_params, dict) + assert optimizer._is_optimized + + +def test_optimize_params_with_custom_get_params(sample_data): + """Test optimize_params with a custom get_params function.""" + optimizer = MQOptimizer() + + def custom_get_params(trial): + return { + "learning_rate": trial.suggest_float("learning_rate", 0.01, 0.1), + "num_leaves": trial.suggest_int("num_leaves", 10, 50), + } + + best_params = optimizer.optimize_params( + dataset=sample_data, n_trials=1, get_params_func=custom_get_params + ) + assert "learning_rate" in best_params + assert "num_leaves" in best_params + + +def test_optimize_params_with_valid_set(sample_data, sample_valid_data): + """Test optimize_params with a provided validation set.""" + optimizer = MQOptimizer() + best_params = optimizer.optimize_params( + dataset=sample_data, n_trials=1, valid_set=sample_valid_data + ) + assert isinstance(best_params, dict) + assert optimizer._is_optimized + + +def test_optimize_params_without_optimization(): + """Test accessing best_params before optimization is completed.""" + optimizer = MQOptimizer() + with pytest.raises(FittingException, match="Optimization is not completed."): + _ = optimizer.best_params + + +def test_study_property_before_optimization(): + """Test accessing study property before optimization.""" + optimizer = MQOptimizer() + assert optimizer.study is None + + +def test_study_property_after_optimization(sample_data): + """Test accessing study property after optimization.""" + optimizer = MQOptimizer() + optimizer.optimize_params(dataset=sample_data, n_trials=1) + assert isinstance(optimizer.study, optuna.Study) + + +def test_mqobjective_property(sample_data): + """Test MQObj property after initialization.""" + optimizer = MQOptimizer() + optimizer._MQObj = MQObjective( + alphas=sample_data.alphas, + objective=optimizer._objective, + model=optimizer._model, + delta=optimizer._delta, + epsilon=optimizer._epsilon, + ) + assert isinstance(optimizer.MQObj, MQObjective) + + +def test_optimize_params_invalid_dataset(): + """Test optimize_params with an invalid dataset.""" + optimizer = MQOptimizer() + with pytest.raises(AttributeError): + optimizer.optimize_params(dataset=None, n_trials=1) + + +def test_optimize_params_invalid_n_trials(sample_data): + """Test optimize_params with invalid n_trials.""" + optimizer = MQOptimizer() + with pytest.raises(ValueError): + optimizer.optimize_params(dataset=sample_data, n_trials=0) + + +def test_lgb_get_params(): + """Test the default LightGBM get_params function.""" + trial = optuna.trial.FixedTrial( + { + "learning_rate": 0.05, + "max_depth": 5, + "lambda_l1": 0.1, + "lambda_l2": 0.1, + "num_leaves": 31, + "feature_fraction": 0.8, + "bagging_fraction": 0.8, + "bagging_freq": 5, + } + ) + params = _lgb_get_params(trial) + expected_keys = { + "verbose", + "learning_rate", + "max_depth", + "lambda_l1", + "lambda_l2", + "num_leaves", + "feature_fraction", + "bagging_fraction", + "bagging_freq", + } + assert set(params.keys()) == expected_keys + + +def test_xgb_get_params(): + """Test the default XGBoost get_params function.""" + trial = optuna.trial.FixedTrial( + { + "learning_rate": 0.05, + "max_depth": 5, + "reg_lambda": 0.1, + "reg_alpha": 0.1, + "subsample": 0.8, + "colsample_bytree": 0.8, + } + ) + params = _xgb_get_params(trial) + expected_keys = { + "learning_rate", + "max_depth", + "reg_lambda", + "reg_alpha", + "subsample", + "colsample_bytree", + } + assert set(params.keys()) == expected_keys diff --git a/tests/test_regressor.py b/tests/test_regressor.py new file mode 100644 index 0000000..07eede8 --- /dev/null +++ b/tests/test_regressor.py @@ -0,0 +1,179 @@ +import lightgbm as lgb +import numpy as np +import pytest +import xgboost as xgb + +from mqboost.base import FittingException, ModelName, ObjectiveName +from mqboost.regressor import MQDataset, MQRegressor + +# Test data and helper functions + + +@pytest.fixture +def dummy_dataset_lgb(): + X = np.random.rand(100, 10) + y = np.random.rand(100) + alphas = [0.1, 0.49, 0.5, 0.51, 0.9] + return MQDataset(alphas=alphas, data=X, label=y, model=ModelName.lightgbm.value) + + +@pytest.fixture +def dummy_eval_set_lgb(): + X_val = np.random.rand(50, 10) + y_val = np.random.rand(50) + alphas = [0.1, 0.49, 0.5, 0.51, 0.9] + return MQDataset( + alphas=alphas, data=X_val, label=y_val, model=ModelName.lightgbm.value + ) + + +@pytest.fixture +def dummy_dataset_xgb(): + X = np.random.rand(100, 10) + y = np.random.rand(100) + alphas = [0.1, 0.49, 0.5, 0.51, 0.9] + return MQDataset(alphas=alphas, data=X, label=y, model=ModelName.xgboost.value) + + +@pytest.fixture +def dummy_eval_set_xgb(): + X_val = np.random.rand(50, 10) + y_val = np.random.rand(50) + alphas = [0.1, 0.49, 0.5, 0.51, 0.9] + return MQDataset( + alphas=alphas, data=X_val, label=y_val, model=ModelName.xgboost.value + ) + + +def test_mqregressor_initialization(): + params = {"learning_rate": 0.1, "num_leaves": 31} + regressor = MQRegressor( + params=params, + model=ModelName.lightgbm.value, + objective=ObjectiveName.check.value, + delta=0.01, + epsilon=1e-5, + ) + assert regressor._params == params + assert regressor._model == ModelName.lightgbm + assert regressor._objective == ObjectiveName.check + assert regressor._delta == 0.01 + assert regressor._epsilon == 1e-5 + + +def test_invalid_model_initialization(): + params = {"learning_rate": 0.1, "num_leaves": 31} + with pytest.raises(ValueError): + MQRegressor( + params=params, + model="invalid_model", + objective=ObjectiveName.check.value, + delta=0.01, + epsilon=1e-5, + ) + + +def test_invalid_objective_initialization(): + params = {"learning_rate": 0.1, "num_leaves": 31} + with pytest.raises(ValueError): + MQRegressor( + params=params, + model=ModelName.lightgbm.value, + objective="invalid_objective", + delta=0.01, + epsilon=1e-5, + ) + + +# Test MQRegressor Fit Method +def test_mqregressor_fit_lgb(dummy_dataset_lgb, dummy_eval_set_lgb): + params = {"learning_rate": 0.1, "num_leaves": 31} + regressor = MQRegressor(params=params, model=ModelName.lightgbm.value) + regressor.fit(dataset=dummy_dataset_lgb, eval_set=dummy_eval_set_lgb) + + assert regressor._fitted is True + assert isinstance(regressor.model, lgb.Booster) + + +def test_mqregressor_fit_xgb(dummy_dataset_xgb, dummy_eval_set_xgb): + params = {"learning_rate": 0.1, "max_depth": 6} + regressor = MQRegressor(params=params, model=ModelName.xgboost.value) + regressor.fit(dataset=dummy_dataset_xgb, eval_set=dummy_eval_set_xgb) + + assert regressor._fitted is True + assert isinstance(regressor.model, xgb.Booster) + + +def test_fit_without_eval_set_lgb(dummy_dataset_lgb): + params = {"learning_rate": 0.1, "num_leaves": 31} + regressor = MQRegressor(params=params, model=ModelName.lightgbm.value) + + regressor.fit(dataset=dummy_dataset_lgb) + assert regressor._fitted is True + assert isinstance(regressor.model, lgb.Booster) + + +def test_fit_without_eval_set_xgb(dummy_dataset_xgb): + params = {"learning_rate": 0.1, "max_depth": 6} + regressor = MQRegressor(params=params, model=ModelName.xgboost.value) + regressor.fit(dataset=dummy_dataset_xgb) + + assert regressor._fitted is True + assert isinstance(regressor.model, xgb.Booster) + + +# Test MQRegressor Predict Method +def test_predict_lgb(dummy_dataset_lgb): + params = {"learning_rate": 0.1, "num_leaves": 31} + regressor = MQRegressor(params=params, model=ModelName.lightgbm.value) + + regressor.fit(dataset=dummy_dataset_lgb) + predictions = regressor.predict(dataset=dummy_dataset_lgb) + assert predictions.shape == (len(dummy_dataset_lgb.alphas), dummy_dataset_lgb.nrow) + + +def test_predict_xgb(dummy_dataset_xgb): + params = {"learning_rate": 0.1, "max_depth": 6} + regressor = MQRegressor(params=params, model=ModelName.xgboost.value) + + regressor.fit(dataset=dummy_dataset_xgb) + predictions = regressor.predict(dataset=dummy_dataset_xgb) + + assert predictions.shape == (len(dummy_dataset_xgb.alphas), dummy_dataset_xgb.nrow) + + +def test_predict_without_fit(dummy_dataset_lgb): + params = {"learning_rate": 0.1, "num_leaves": 31} + regressor = MQRegressor(params=params, model=ModelName.lightgbm.value) + + with pytest.raises(FittingException): + regressor.predict(dataset=dummy_dataset_lgb) + + +# Test Monotonicity Constraints +def test_monotone_constraints_called_lgb(dummy_dataset_lgb): + params = {"learning_rate": 0.1, "num_leaves": 31} + regressor = MQRegressor(params=params, model=ModelName.lightgbm.value) + + regressor.fit(dataset=dummy_dataset_lgb) + predictions = regressor.predict(dataset=dummy_dataset_lgb) + assert np.all( + [ + np.all(predictions[k] <= predictions[k + 1]) + for k in range(len(predictions) - 1) + ] + ) + + +def test_monotone_constraints_called_xgb(dummy_dataset_xgb): + params = {"learning_rate": 0.1, "max_depth": 6} + regressor = MQRegressor(params=params, model=ModelName.xgboost.value) + + regressor.fit(dataset=dummy_dataset_xgb) + predictions = regressor.predict(dataset=dummy_dataset_xgb) + assert np.all( + [ + np.all(predictions[k] <= predictions[k + 1]) + for k in range(len(predictions) - 1) + ] + ) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..1b778d5 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,174 @@ +import numpy as np +import pandas as pd +import pytest + +from mqboost.base import MQStr, ValidationException +from mqboost.utils import ( + alpha_validate, + delta_validate, + params_validate, + prepare_x, + prepare_y, +) + + +# Test for alpha_validate +def test_alpha_validate_single_alpha(): + alphas = 0.3 + result = alpha_validate(alphas) + assert result == [0.3] + + +def test_alpha_validate_multiple_alphas(): + alphas = [0.1, 0.2, 0.3] + result = alpha_validate(alphas) + assert result == alphas + + +def test_alpha_validate_raises_on_zero_or_one_alpha(): + with pytest.raises(ValidationException, match="Alpha cannot be 0 or 1"): + alpha_validate([0.0, 0.3]) + with pytest.raises(ValidationException, match="Alpha cannot be 0 or 1"): + alpha_validate([0.3, 1.0]) + + +def test_alpha_validate_raises_on_non_ascending_alphas(): + with pytest.raises(ValidationException, match="Alpha is not ascending order"): + alpha_validate([0.3, 0.2, 0.1]) + + +def test_alpha_validate_raises_on_duplicate_alphas(): + with pytest.raises(ValidationException, match="Duplicated alpha exists"): + alpha_validate([0.1, 0.2, 0.2]) + + +def test_alpha_validate_raises_on_empty_alphas(): + with pytest.raises(ValidationException, match="Input alpha is not valid"): + alpha_validate([]) + + +# Test for prepare_x +def test_prepare_x_with_dataframe(): + x = pd.DataFrame( + { + "feature_1": [1, 2], + "feature_2": [3, 4], + } + ) + alphas = [0.1, 0.2] + result = prepare_x(x, alphas) + + expected = pd.DataFrame( + { + "feature_1": [1, 2, 1, 2], + "feature_2": [3, 4, 3, 4], + "_tau": [0.1, 0.1, 0.2, 0.2], + } + ) + + pd.testing.assert_frame_equal(result, expected) + + +def test_prepare_x_with_series(): + x = pd.Series([1, 2, 3]) + alphas = [0.1, 0.2] + result = prepare_x(x, alphas) + + expected = pd.DataFrame( + { + 0: [1, 2, 3, 1, 2, 3], + "_tau": [0.1, 0.1, 0.1, 0.2, 0.2, 0.2], + } + ) + + pd.testing.assert_frame_equal(result, expected) + + +def test_prepare_x_with_array(): + x = np.array([[1, 2], [3, 4]]) + alphas = [0.1, 0.2] + result = prepare_x(x, alphas) + + expected = pd.DataFrame( + { + 0: [1, 3, 1, 3], + 1: [2, 4, 2, 4], + "_tau": [0.1, 0.1, 0.2, 0.2], + } + ) + + pd.testing.assert_frame_equal(result, expected) + + +def test_prepare_x_raises_on_invalid_column_name(): + x = pd.DataFrame({"_tau": [1, 2], "feature_1": [3, 4]}) + alphas = [0.1, 0.2] + + with pytest.raises(ValidationException, match="Column name '_tau' is not allowed."): + prepare_x(x, alphas) + + +# Test for prepare_y +def test_prepare_y_with_list(): + y = [1, 2, 3] + alphas = [0.1, 0.2] + result = prepare_y(y, alphas) + + expected = np.array([1, 2, 3, 1, 2, 3]) + + np.testing.assert_array_equal(result, expected) + + +def test_prepare_y_with_array(): + y = np.array([1, 2, 3]) + alphas = [0.1, 0.2] + result = prepare_y(y, alphas) + + expected = np.array([1, 2, 3, 1, 2, 3]) + + np.testing.assert_array_equal(result, expected) + + +def test_prepare_y_with_series(): + y = pd.Series([1, 2, 3]) + alphas = [0.1, 0.2] + result = prepare_y(y, alphas) + + expected = np.array([1, 2, 3, 1, 2, 3]) + + np.testing.assert_array_equal(result, expected) + + +# Test for delta_validate +def test_delta_validate_valid_delta(): + delta = 0.04 + assert delta_validate(delta) is None + + +def test_delta_validate_invalid_type(): + with pytest.raises(ValidationException, match="Delta is not float type"): + delta_validate(1) + + +def test_delta_validate_negative_delta(): + with pytest.raises(ValidationException, match="Delta must be positive"): + delta_validate(-0.01) + + +def test_delta_validate_exceeds_upper_bound(): + delta = 0.06 + with pytest.warns(UserWarning, match="Delta should be 0.05 or less."): + delta_validate(delta) + + +# Test for params validate +def test_set_params_validate_raises_validation_exception(): + params = { + MQStr.obj.value: "regression", + MQStr.mono.value: [1, -1], + } + with pytest.raises( + ValidationException, + match="The parameter named 'objective' must be excluded in params", + ): + params_validate(params)