diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4d36623d..0bbbc915 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,7 +2,7 @@ name: docs on: workflow_run: - workflows: ["CI"] + workflows: ["tests"] types: - completed @@ -14,12 +14,12 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v3 - + - name: Setup Python uses: actions/setup-python@v3 with: python-version: 3.x - + - name: Install dependencies run: pip install mkdocs-material mkdocs-git-revision-date-localized-plugin @@ -36,7 +36,7 @@ jobs: - name: Download Coverage Report uses: dawidd6/action-download-artifact@v2 with: - workflow: main.yml + workflow: tests.yml name: coverage path: coverage search_artifacts: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e671ef32..0b329beb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,6 +1,10 @@ name: lint -on: [push, pull_request] +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] jobs: lint: @@ -9,13 +13,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Set up Python 3.11 + - name: Set up Python 3.8 uses: actions/setup-python@v3 with: - python-version: "3.11" - - name: Install dependencies - run: python3.11 -m pip install -r requirements-dev.txt - - name: Install mypy types - run: mypy --install-types --non-interactive -p modelkit + python-version: "3.8" + - name: Setup lint + run: make setup_lint - name: Run lint - run: scripts/lint.sh + run: make lint diff --git a/.github/workflows/main.yml b/.github/workflows/tests.yml similarity index 90% rename from .github/workflows/main.yml rename to .github/workflows/tests.yml index 01e20e5f..fc5fab9b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/tests.yml @@ -1,13 +1,17 @@ -name: CI - -on: [push, pull_request] +name: tests defaults: run: shell: bash +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + jobs: - test: + tests: strategy: fail-fast: false matrix: @@ -25,11 +29,8 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Install Dependencies - run: python -m pip install --upgrade nox - - name: Run Tests - run: scripts/ci_tests.sh + run: make ci_tests env: OS_NAME: "${{ matrix.os }}" PYTHON_VERSION: "${{ matrix.python-version }}" diff --git a/.gitignore b/.gitignore index 3f1301c7..f9f0a74f 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,9 @@ output/*/index.html # pytest .pytest_cache +# ruff +.ruff_cache + # tmp files /tmp/ /*.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..8c472887 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,47 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + # - id: trailing-whitespace # TODO: enable this + # - id: end-of-file-fixer # TODO: enable this + - id: debug-statements + + - repo: https://github.com/psf/black + rev: 23.7.0 + hooks: + - id: black + args: ["--target-version", "py38"] + + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: 'v0.0.290' + hooks: + - id: ruff + args: ["--fix"] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.5.1 + hooks: + - id: mypy + types: [python] + args: [] # TODO: enable this: "--check-untyped-defs" + # TODO: narrow tests exclusion down to: ^tests/testdata/typing/*$ + exclude: | + (?x)^( + .vulture.py| + tests/.* + )$ + additional_dependencies: [ + pydantic==1.*, + types-python-dateutil, + types-requests, + types-urllib3, + types-redis, + types-cachetools, + types-tabulate + ] + + - repo: https://github.com/jendrikseipp/vulture + rev: v2.9.1 + hooks: + - id: vulture + args: ["--min-confidence", "90", "modelkit", "bin", ".vulture.py"] diff --git a/.vulture.py b/.vulture.py new file mode 100644 index 00000000..e69de29b diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..e0e8ed89 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] +recursive-exclude tests * diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..3ba746f9 --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +SHELL := /bin/bash +.SHELLFLAGS := -e -x -c -o pipefail +.PHONY: setup setup_lint lint tests coverage ci_tests requirements upgrade + +setup_lint: + pip install --upgrade pip pre-commit + pre-commit install + +lint: + pre-commit run --all-files + +tests: + export ENABLE_TF_SERVING_TEST=True; \ + export ENABLE_TF_TEST=True; \ + export ENABLE_REDIS_TEST=True; \ + export ENABLE_S3_TEST=True; \ + export ENABLE_GCS_TEST=True; \ + export ENABLE_AZ_TEST=True; \ + pytest + +coverage: + export ENABLE_TF_SERVING_TEST=True; \ + export ENABLE_TF_TEST=True; \ + export ENABLE_REDIS_TEST=True; \ + export ENABLE_S3_TEST=True; \ + export ENABLE_GCS_TEST=True; \ + export ENABLE_AZ_TEST=True; \ + coverage run -m pytest; \ + coverage report -m; \ + coverage xml + +setup: + pip install --upgrade pip + pip install -r requirements-dev.txt + pip install -e .[lint,tensorflow,cli,api,assets-s3,assets-gcs,assets-az] + pre-commit install + +ci_tests: + pip install --upgrade pip nox + @if [ "$(OS_NAME)" = "ubuntu-latest" ]; then \ + export ENABLE_TF_SERVING_TEST=True; \ + export ENABLE_TF_TEST=True; \ + export ENABLE_REDIS_TEST=True; \ + export ENABLE_S3_TEST=True; \ + export ENABLE_GCS_TEST=True; \ + export ENABLE_AZ_TEST=True; \ + nox --error-on-missing-interpreters -s "coverage-$(PYTHON_VERSION)"; \ + else \ + nox --error-on-missing-interpreters -s "tests-$(PYTHON_VERSION)"; \ + fi + +requirements: + pip install --upgrade pip pip-compile + pip-compile --extra=dev --output-file=requirements-dev.txt + +upgrade: + pip install --upgrade pip pip-tools pre-commit + pre-commit autoupdate + pip-compile --upgrade --extra=dev --output-file=requirements-dev.txt + pip install -r requirements-dev.txt diff --git a/docs/contributions/release.md b/docs/contributions/release.md index ac2670f2..fdf0821d 100644 --- a/docs/contributions/release.md +++ b/docs/contributions/release.md @@ -10,9 +10,13 @@ git switch -C releasing central/main ## Bump version -We use `bump2version` to create the commit and tag required for the release +We use `bump-my-version` to create the commit and tag required for the release ```bash -bump2version patch +bump-my-version bump patch +``` +or via its alias: +```bash +bumpversion bump patch ``` ## Push commit and tag to central @@ -21,7 +25,7 @@ bump2version patch git push --follow-tags central releasing:main # Or more concisely if you have configured git with push.default = upstream -git push --follow-tags +git push --follow-tags ``` ## Package and publish new artifact to pypi diff --git a/modelkit/api.py b/modelkit/api.py index a5d4b8dc..edf56688 100644 --- a/modelkit/api.py +++ b/modelkit/api.py @@ -129,7 +129,7 @@ def _make_model_endpoint_fn(self, model, item_type): async def _aendpoint( item: item_type = fastapi.Body(...), model=fastapi.Depends(lambda: self.lib.get(model.configuration_key)), - ): # noqa: B008 + ): return await model.predict(item) return _aendpoint @@ -137,7 +137,7 @@ async def _aendpoint( def _endpoint( item: item_type = fastapi.Body(...), model=fastapi.Depends(lambda: self.lib.get(model.configuration_key)), - ): # noqa: B008 + ): return model.predict(item) return _endpoint @@ -148,7 +148,7 @@ def _make_batch_model_endpoint_fn(self, model, item_type): async def _aendpoint( item: List[item_type] = fastapi.Body(...), model=fastapi.Depends(lambda: self.lib.get(model.configuration_key)), - ): # noqa: B008 + ): return await model.predict_batch(item) return _aendpoint @@ -156,7 +156,7 @@ async def _aendpoint( def _endpoint( item: List[item_type] = fastapi.Body(...), model=fastapi.Depends(lambda: self.lib.get(model.configuration_key)), - ): # noqa: B008 + ): return model.predict_batch(item) return _endpoint diff --git a/modelkit/assets/drivers/gcs.py b/modelkit/assets/drivers/gcs.py index cdb3202e..7e869295 100644 --- a/modelkit/assets/drivers/gcs.py +++ b/modelkit/assets/drivers/gcs.py @@ -72,14 +72,14 @@ def download_object(self, object_name, destination_path): try: with open(destination_path, "wb") as f: blob.download_to_file(f) - except NotFound: + except NotFound as e: logger.error( "Object not found.", bucket=self.bucket, object_name=object_name ) os.remove(destination_path) raise errors.ObjectDoesNotExistError( driver=self, bucket=self.bucket, object_name=object_name - ) + ) from e @retry(**GCS_RETRY_POLICY) def delete_object(self, object_name): diff --git a/modelkit/assets/drivers/s3.py b/modelkit/assets/drivers/s3.py index 1befdb12..f6fc995b 100644 --- a/modelkit/assets/drivers/s3.py +++ b/modelkit/assets/drivers/s3.py @@ -83,14 +83,14 @@ def download_object(self, object_name, destination_path): try: with open(destination_path, "wb") as f: self.client.download_fileobj(self.bucket, object_name, f) - except botocore.exceptions.ClientError: + except botocore.exceptions.ClientError as e: logger.error( "Object not found.", bucket=self.bucket, object_name=object_name ) os.remove(destination_path) raise errors.ObjectDoesNotExistError( driver=self, bucket=self.bucket, object_name=object_name - ) + ) from e @retry(**S3_RETRY_POLICY) def delete_object(self, object_name): diff --git a/modelkit/core/errors.py b/modelkit/core/errors.py index 6d9129ec..3e6c550b 100644 --- a/modelkit/core/errors.py +++ b/modelkit/core/errors.py @@ -114,8 +114,8 @@ def wrapper(*args, **kwargs): return func(*args, **kwargs) except PredictionError as exc: if os.environ.get("MODELKIT_ENABLE_SIMPLE_TRACEBACK", "True") == "True": - raise strip_modelkit_traceback_frames(exc.exc) - raise exc.exc + raise strip_modelkit_traceback_frames(exc.exc) from exc + raise exc.exc from exc except BaseException: raise @@ -132,8 +132,8 @@ def wrapper(*args, **kwargs): yield from func(*args, **kwargs) except PredictionError as exc: if os.environ.get("MODELKIT_ENABLE_SIMPLE_TRACEBACK", "True") == "True": - raise strip_modelkit_traceback_frames(exc.exc) - raise exc.exc + raise strip_modelkit_traceback_frames(exc.exc) from exc + raise exc.exc from exc except BaseException: raise @@ -150,8 +150,8 @@ async def wrapper(*args, **kwargs): return await func(*args, **kwargs) except PredictionError as exc: if os.environ.get("MODELKIT_ENABLE_SIMPLE_TRACEBACK", "True") == "True": - raise strip_modelkit_traceback_frames(exc.exc) - raise exc.exc + raise strip_modelkit_traceback_frames(exc.exc) from exc + raise exc.exc from exc except BaseException: raise @@ -170,8 +170,8 @@ async def wrapper(*args, **kwargs): yield x except PredictionError as exc: if os.environ.get("MODELKIT_ENABLE_SIMPLE_TRACEBACK", "True") == "True": - raise strip_modelkit_traceback_frames(exc.exc) - raise exc.exc + raise strip_modelkit_traceback_frames(exc.exc) from exc + raise exc.exc from exc except BaseException: raise diff --git a/modelkit/core/library.py b/modelkit/core/library.py index b40a238c..a8154be1 100644 --- a/modelkit/core/library.py +++ b/modelkit/core/library.py @@ -107,7 +107,7 @@ def __init__( self.cache = RedisCache( self.settings.cache.host, self.settings.cache.port ) - except (ConnectionError, redis.ConnectionError): + except (ConnectionError, redis.ConnectionError) as e: logger.error( "Cannot ping redis instance", cache_host=self.settings.cache.host, @@ -117,7 +117,7 @@ def __init__( "Cannot ping redis instance" f"[cache_host={self.settings.cache.host}, " f"port={self.settings.cache.port}]" - ) + ) from e if isinstance(self.settings.cache, NativeCacheSettings): self.cache = NativeCache( self.settings.cache.implementation, self.settings.cache.maxsize @@ -354,7 +354,7 @@ def _resolve_assets(self, model_name): def preload(self): # make sure the assets_manager is instantiated - self.assets_manager + _ = self.assets_manager for model_name in self.required_models: self._load(model_name) diff --git a/modelkit/core/model.py b/modelkit/core/model.py index 0266a41f..5ed5d8f5 100644 --- a/modelkit/core/model.py +++ b/modelkit/core/model.py @@ -255,7 +255,7 @@ def initialize_validation_models(self): except Exception as exc: # pragma: no cover raise errors.ValidationInitializationException( f"{self.__class__.__name__}[{self.configuration_key}]", pydantic_exc=exc - ) + ) from exc def __getstate__(self): state = copy.deepcopy(self.__dict__) @@ -400,7 +400,7 @@ def _validate( raise exception( f"{self.__class__.__name__}[{self.configuration_key}]", pydantic_exc=exc, - ) + ) from exc return item def test(self): @@ -632,7 +632,7 @@ def _predict_cache_items( try: predictions = iter(self._predict_batch(batch, **kwargs)) except BaseException as exc: - raise errors.PredictionError(exc=exc) + raise errors.PredictionError(exc=exc) from exc current_predictions = [] try: for cache_item in cache_items: @@ -696,7 +696,7 @@ async def predict( _force_compute: bool = False, **kwargs, ) -> ReturnType: - async for r in self.predict_gen( + async for r in self.predict_gen( # noqa: B007 iter((item,)), _force_compute=_force_compute, __internal=True, **kwargs ): break @@ -806,7 +806,7 @@ async def _predict_cache_items( try: predictions = iter(await self._predict_batch(batch, **kwargs)) except BaseException as exc: - raise errors.PredictionError(exc=exc) + raise errors.PredictionError(exc=exc) from exc current_predictions = [] try: for cache_item in cache_items: diff --git a/modelkit/testing/reference.py b/modelkit/testing/reference.py index dd97b204..2fdd046b 100644 --- a/modelkit/testing/reference.py +++ b/modelkit/testing/reference.py @@ -114,7 +114,7 @@ def click_invoke(runner, cmd_fn, args, env=None): def deep_format_floats(obj, depth=5): # case str, is container but should be returned as is - if type(obj) is str: + if isinstance(obj, str): return obj # deep recursive call for any container elif isinstance(obj, dict): diff --git a/modelkit/testing/tf_serving.py b/modelkit/testing/tf_serving.py index 8d96ca42..05816a86 100644 --- a/modelkit/testing/tf_serving.py +++ b/modelkit/testing/tf_serving.py @@ -60,11 +60,9 @@ def finalize(): request.addfinalizer(finalize) connect_tf_serving( next( - ( - x - for x in lib.required_models - if issubclass(lib.configuration[x].model_type, TensorflowModel) - ) + x + for x in lib.required_models + if issubclass(lib.configuration[x].model_type, TensorflowModel) ), "localhost", 8500, diff --git a/modelkit/utils/logging.py b/modelkit/utils/logging.py index 912ea0b5..37619306 100644 --- a/modelkit/utils/logging.py +++ b/modelkit/utils/logging.py @@ -12,6 +12,6 @@ def __enter__(self): contextvars.bind_contextvars(**self._context) return self - def __exit__(self, exc_type, exc_value, exc_traceback): + def __exit__(self, exc_type, exc_value, exc_traceback): # noqa: F841 [contextvars.unbind_contextvars(key) for key in self._context] contextvars.bind_contextvars(**self._existing_vars) diff --git a/noxfile.py b/noxfile.py index 88921c24..11a93d6c 100644 --- a/noxfile.py +++ b/noxfile.py @@ -2,10 +2,10 @@ @nox.session(python=["3.8", "3.9", "3.10", "3.11"]) -def test(session): +def tests(session): # Install deps and the package itself. + session.install(".[cli,api,assets-gcs,assets-s3,assets-az]") session.install("-r", "requirements-dev.txt") - session.run("mypy", "--install-types", "--non-interactive", "-p", "modelkit") session.run("pytest", "--junitxml=junit.xml") @@ -13,8 +13,8 @@ def test(session): @nox.session(python=["3.8", "3.9", "3.10", "3.11"]) def coverage(session): # Install deps and the package itself. - session.install("-r", "requirements-optional.txt") - session.run("mypy", "--install-types", "--non-interactive", "-p", "modelkit") + session.install(".[cli,api,tensorflow,assets-s3,assets-gcs,assets-az]") + session.install("-r", "requirements-dev.txt") session.run("coverage", "run", "-m", "pytest", "--junitxml=junit.xml") session.run("coverage", "report", "-m") diff --git a/pyproject.toml b/pyproject.toml index 1a4fb93c..3fd0698b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,23 +1,202 @@ [build-system] -requires = ["setuptools >= 40.6.0", "wheel"] +requires = ["setuptools >= 40.6.0", "wheel >=0.38.1"] build-backend = "setuptools.build_meta" +[project] +name = "modelkit" +description = "Minimalistic MLOps library for python" +readme = "README.md" +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] +requires-python = ">=3.8" +dynamic = ["version"] +dependencies = [ + "aiohttp", + "asgiref", + "cachetools", + "click", + "filelock", + "humanize", + "pydantic<2.0", + "python-dateutil", + "redis", + "requests", + "rich", + "sniffio", + "structlog", + "tenacity", + "typing_extensions", + "tabulate", +] +[tool.setuptools.dynamic] +version = {attr = "modelkit.__version__"} + +[project.urls] +Homepage = "https://github.com/Cornerstone-OnDemand/modelkit" +"Bug Tracker" = "https://github.com/Cornerstone-OnDemand/modelkit/issues" +Documentation = "https://Cornerstone-OnDemand.github.io/modelkit/" +"Source Code" = "https://github.com/Cornerstone-OnDemand/modelkit" + +[project.optional-dependencies] +tensorflow = [ + "numpy", + "grpcio", + "tensorflow", + "tensorflow-serving-api", +] +cli = [ + "networkx", + "memory-profiler", + "fastapi", + "uvicorn", +] +api = [ + "fastapi", + "uvicorn", +] +assets-s3 = ["boto3"] +assets-gcs = ["google-cloud-storage"] +assets-az = ["azure-storage-blob", "cryptography>=41.0.2"] +lint = [ + "black", + "flake8", + "ruff", + "mypy", + "types-requests", + "types-python-dateutil", + "types-cachetools", + "types-redis", + "types-tabulate", +] +dev = [ + # tests, + "httpx", # for starlette TestClient + # needed for test_typing.py + "mypy", + "types-requests", + "types-python-dateutil", + "types-cachetools", + "types-redis", + "types-tabulate", + # lint + "pre-commit", + # tests + "coverage", + "pytest", + "pytest-asyncio", + "nox", + # releases + "bump-my-version", + # docs + "mkdocs-material", + "pymdown-extensions>=10.0", # resolve CVE-2023-32309 + # build + "wheel>=0.38.1", # resolve CVE-2022-40898 + # requirements + "pip-tools", +] + +[project.scripts] +modelkit = "modelkit.cli:modelkit_cli" + +[tool.setuptools] +zip-safe = false +include-package-data = true + +[tool.setuptools.packages.find] +include = ["modelkit*"] +exclude = ["tests*", "docs*", "bin*", "scripts*"] +namespaces = false + +[tool.setuptools.package-data] +modelkit = ["py.typed"] + +[tool.pytest.ini_options] +addopts = """ +--strict +--verbose +--tb=native +-vv +--failed-first +--disable-warnings +--durations 10 +--color=yes +tests""" + [tool.black] target-version = ['py38'] -[tool.towncrier] -package = "modelkit" -filename = "HISTORY.md" -directory = "newsfragments" -underlines = ["", "", ""] -title_format = "# {version} ({project_date})" -start_string = "" -template = "towncrier-template.md" - -# Hack towncrier to output md using a single type named "md" because we don't -# want categories but we want a .md extension. The section/type won't be -# displayed, as configured in towncrier-template.md. -[[tool.towncrier.type]] -directory = "md" -name = "" -showcontent = true +[tool.isort] +profile = "black" + +[tool.flake8] +max-line-length = "88" +extend-ignore = "E203" + +[tool.coverage.run] +source = ["modelkit"] +omit = [ + "modelkit/assets/cli.py", + "modelkit/cli.py", +] + +[tool.coverage.report] +fail_under = 90 +precision = 2 + +[tool.mypy] +ignore_missing_imports = true +plugins = ["pydantic.mypy"] + +[[tool.mypy.overrides]] +module = ["azure.storage.blob.*"] +ignore_errors = true + +[tool.bumpversion] +commit = true +tag = true +tag_name = "{new_version}" +allow_dirty = true +message = "Bump version: {current_version} → {new_version}" + +[[tool.bumpversion.files]] +filename = "modelkit/__init__.py" + +[tool.ruff] +select = [ + "B", # flake8-bug-bear + "E", # pycodestyle + "W", # pycodestyle + "F", # pyflakes +] +extend-select = [ + "I", # isort + "UP", # pyupgrade +] +ignore = [ + "B011", + "E741", + "W605", +] +line-length = 88 +target-version = "py38" +show-source = false +src = ["modelkit", "tests", "bin"] + +[tool.ruff.isort] +known-first-party = ["modelkit", "tests", "bin"] +known-third-party = [] + +[tool.ruff.flake8-bugbear] +extend-immutable-calls = [ + "fastapi.Body", + "fastapi.Header", + "fastapi.Path", + "fastapi.Query", + "fastapi.Depends" +] diff --git a/requirements-dev.in b/requirements-dev.in deleted file mode 100644 index 82365709..00000000 --- a/requirements-dev.in +++ /dev/null @@ -1,39 +0,0 @@ --c requirements.txt --r requirements.in - -# tests -mypy -coverage -pytest -pytest-asyncio -black -flake8<4 # flake8==4.0.1 pins importlib-metadata<4.3 which leads to a conflict -isort -nox -boto3 -google-api-core -google-cloud-storage -azure-storage-blob -tenacity -types-requests -types-python-dateutil -types-cachetools -types-redis -types-tabulate -# cli -memory-profiler -networkx -# api -uvicorn -fastapi -httpx -# releases -bump2version -# docs -mkdocs-material -pymdown-extensions>=10.0 # resolve CVE-2023-32309 -wheel>=0.38.1 # resolve CVE-2022-40898 -# pip -pip-tools -# azure deps -cryptography>=41.0.2 # resolve CVE-2023-0286 diff --git a/requirements-dev.txt b/requirements-dev.txt index 2dd91b76..d425f04b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,366 +2,259 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-emit-index-url --output-file=requirements-dev.txt requirements-dev.in +# pip-compile --extra=dev --output-file=requirements-dev.txt # aiohttp==3.8.5 - # via - # -c requirements.txt - # -r requirements.in + # via modelkit (pyproject.toml) aiosignal==1.3.1 - # via - # -c requirements.txt - # aiohttp -anyio==3.7.1 - # via - # httpcore - # starlette -argcomplete==3.1.1 + # via aiohttp +anyio==4.0.0 + # via httpcore +argcomplete==3.1.2 # via nox asgiref==3.7.2 + # via modelkit (pyproject.toml) +async-timeout==4.0.3 # via - # -c requirements.txt - # -r requirements.in -async-timeout==4.0.2 - # via - # -c requirements.txt # aiohttp # redis attrs==23.1.0 - # via - # -c requirements.txt - # aiohttp -azure-core==1.28.0 - # via azure-storage-blob -azure-storage-blob==12.17.0 - # via -r requirements-dev.in -black==23.7.0 - # via -r requirements-dev.in -boto3==1.28.10 - # via -r requirements-dev.in -botocore==1.31.10 - # via - # boto3 - # s3transfer -build==0.10.0 + # via aiohttp +babel==2.12.1 + # via mkdocs-material +build==1.0.3 # via pip-tools -bump2version==1.0.1 - # via -r requirements-dev.in +bump-my-version==0.10.0 + # via modelkit (pyproject.toml) cachetools==5.3.1 - # via - # -c requirements.txt - # -r requirements.in - # google-auth + # via modelkit (pyproject.toml) certifi==2023.7.22 # via - # -c requirements.txt # httpcore # httpx # requests cffi==1.15.1 # via cryptography +cfgv==3.4.0 + # via pre-commit charset-normalizer==3.2.0 # via - # -c requirements.txt # aiohttp # requests -click==8.1.6 +click==8.1.7 # via - # -c requirements.txt - # -r requirements.in - # black + # bump-my-version # mkdocs + # modelkit (pyproject.toml) # pip-tools - # uvicorn + # rich-click colorama==0.4.6 # via mkdocs-material colorlog==6.7.0 # via nox -coverage==7.2.7 - # via -r requirements-dev.in -cryptography==41.0.2 +coverage==7.3.1 + # via modelkit (pyproject.toml) +cryptography==41.0.4 # via - # -r requirements-dev.in - # azure-storage-blob # types-pyopenssl # types-redis distlib==0.3.7 # via virtualenv -fastapi==0.100.0 - # via -r requirements-dev.in -filelock==3.12.2 +exceptiongroup==1.1.3 # via - # -c requirements.txt - # -r requirements.in + # anyio + # pytest +filelock==3.12.4 + # via + # modelkit (pyproject.toml) # virtualenv -flake8==3.9.2 - # via -r requirements-dev.in frozenlist==1.4.0 # via - # -c requirements.txt # aiohttp # aiosignal ghp-import==2.1.0 # via mkdocs -google-api-core==2.11.1 - # via - # -r requirements-dev.in - # google-cloud-core - # google-cloud-storage -google-auth==2.22.0 - # via - # google-api-core - # google-cloud-core - # google-cloud-storage -google-cloud-core==2.3.3 - # via google-cloud-storage -google-cloud-storage==2.10.0 - # via -r requirements-dev.in -google-crc32c==1.5.0 - # via google-resumable-media -google-resumable-media==2.5.0 - # via google-cloud-storage -googleapis-common-protos==1.59.1 - # via google-api-core h11==0.14.0 - # via - # httpcore - # uvicorn -httpcore==0.17.3 + # via httpcore +httpcore==0.18.0 # via httpx -httpx==0.24.1 - # via -r requirements-dev.in -humanize==4.7.0 - # via - # -c requirements.txt - # -r requirements.in +httpx==0.25.0 + # via modelkit (pyproject.toml) +humanize==4.8.0 + # via modelkit (pyproject.toml) +identify==2.5.29 + # via pre-commit idna==3.4 # via - # -c requirements.txt # anyio # httpx # requests # yarl iniconfig==2.0.0 # via pytest -isodate==0.6.1 - # via azure-storage-blob -isort==5.12.0 - # via -r requirements-dev.in jinja2==3.1.2 # via # mkdocs # mkdocs-material -jmespath==1.0.1 - # via - # boto3 - # botocore -markdown==3.3.7 +markdown==3.4.4 # via # mkdocs # mkdocs-material # pymdown-extensions markdown-it-py==3.0.0 - # via - # -c requirements.txt - # rich + # via rich markupsafe==2.1.3 - # via jinja2 -mccabe==0.6.1 - # via flake8 -mdurl==0.1.2 # via - # -c requirements.txt - # markdown-it-py -memory-profiler==0.61.0 - # via -r requirements-dev.in + # jinja2 + # mkdocs +mdurl==0.1.2 + # via markdown-it-py mergedeep==1.3.4 # via mkdocs -mkdocs==1.4.3 +mkdocs==1.5.3 # via mkdocs-material -mkdocs-material==9.1.19 - # via -r requirements-dev.in -mkdocs-material-extensions==1.1.1 +mkdocs-material==9.3.2 + # via modelkit (pyproject.toml) +mkdocs-material-extensions==1.2 # via mkdocs-material multidict==6.0.4 # via - # -c requirements.txt # aiohttp # yarl -mypy==1.4.1 - # via -r requirements-dev.in +mypy==1.5.1 + # via modelkit (pyproject.toml) mypy-extensions==1.0.0 - # via - # black - # mypy -networkx==3.1 - # via -r requirements-dev.in + # via mypy +nodeenv==1.8.0 + # via pre-commit nox==2023.4.22 - # via -r requirements-dev.in + # via modelkit (pyproject.toml) packaging==23.1 # via - # black # build # mkdocs # nox # pytest -pathspec==0.11.1 - # via black -pip-tools==7.1.0 - # via -r requirements-dev.in -platformdirs==3.9.1 +paginate==0.5.6 + # via mkdocs-material +pathspec==0.11.2 + # via mkdocs +pip-tools==7.3.0 + # via modelkit (pyproject.toml) +platformdirs==3.10.0 # via - # black + # mkdocs # virtualenv -pluggy==1.2.0 +pluggy==1.3.0 # via pytest -protobuf==4.23.4 - # via - # google-api-core - # googleapis-common-protos -psutil==5.9.5 - # via memory-profiler -pyasn1==0.5.0 - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.3.0 - # via google-auth -pycodestyle==2.7.0 - # via flake8 +pre-commit==3.4.0 + # via modelkit (pyproject.toml) pycparser==2.21 # via cffi pydantic==1.10.12 # via - # -c requirements.txt - # -r requirements.in - # fastapi -pyflakes==2.3.1 - # via flake8 -pygments==2.15.1 + # bump-my-version + # modelkit (pyproject.toml) +pygments==2.16.1 # via - # -c requirements.txt # mkdocs-material # rich -pymdown-extensions==10.1 +pymdown-extensions==10.3 # via - # -r requirements-dev.in # mkdocs-material + # modelkit (pyproject.toml) pyproject-hooks==1.0.0 # via build -pytest==7.4.0 +pytest==7.4.2 # via - # -r requirements-dev.in + # modelkit (pyproject.toml) # pytest-asyncio pytest-asyncio==0.21.1 - # via -r requirements-dev.in + # via modelkit (pyproject.toml) python-dateutil==2.8.2 # via - # -c requirements.txt - # -r requirements.in - # botocore # ghp-import + # modelkit (pyproject.toml) pyyaml==6.0.1 # via # mkdocs + # pre-commit # pymdown-extensions # pyyaml-env-tag pyyaml-env-tag==0.1 # via mkdocs -redis==4.6.0 - # via - # -c requirements.txt - # -r requirements.in -regex==2023.6.3 +redis==5.0.0 + # via modelkit (pyproject.toml) +regex==2022.10.31 # via mkdocs-material requests==2.31.0 # via - # -c requirements.txt - # -r requirements.in - # azure-core - # google-api-core - # google-cloud-storage # mkdocs-material -rich==13.4.2 - # via - # -c requirements.txt - # -r requirements.in -rsa==4.9 - # via google-auth -s3transfer==0.6.1 - # via boto3 + # modelkit (pyproject.toml) +rich==13.5.3 + # via + # bump-my-version + # modelkit (pyproject.toml) + # rich-click +rich-click==1.6.1 + # via bump-my-version six==1.16.0 - # via - # -c requirements.txt - # azure-core - # google-auth - # isodate - # python-dateutil + # via python-dateutil sniffio==1.3.0 # via - # -c requirements.txt - # -r requirements.in # anyio # httpcore # httpx -starlette==0.27.0 - # via fastapi + # modelkit (pyproject.toml) structlog==23.1.0 - # via - # -c requirements.txt - # -r requirements.in + # via modelkit (pyproject.toml) tabulate==0.9.0 + # via modelkit (pyproject.toml) +tenacity==8.2.3 + # via modelkit (pyproject.toml) +tomli==2.0.1 # via - # -c requirements.txt - # -r requirements.in -tenacity==8.2.2 - # via - # -c requirements.txt - # -r requirements-dev.in - # -r requirements.in + # build + # mypy + # pip-tools + # pyproject-hooks + # pytest +tomlkit==0.12.1 + # via bump-my-version types-cachetools==5.3.0.6 - # via -r requirements-dev.in + # via modelkit (pyproject.toml) types-pyopenssl==23.2.0.2 # via types-redis types-python-dateutil==2.8.19.14 - # via -r requirements-dev.in -types-redis==4.6.0.3 - # via -r requirements-dev.in -types-requests==2.31.0.2 - # via -r requirements-dev.in + # via modelkit (pyproject.toml) +types-redis==4.6.0.6 + # via modelkit (pyproject.toml) +types-requests==2.31.0.3 + # via modelkit (pyproject.toml) types-tabulate==0.9.0.3 - # via -r requirements-dev.in + # via modelkit (pyproject.toml) types-urllib3==1.26.25.14 # via types-requests -typing-extensions==4.7.1 +typing-extensions==4.8.0 # via - # -c requirements.txt - # -r requirements.in - # azure-core - # azure-storage-blob - # fastapi + # asgiref + # modelkit (pyproject.toml) # mypy # pydantic -urllib3==1.26.12 +urllib3==2.0.5 + # via requests +virtualenv==20.24.5 # via - # -c requirements.txt - # botocore - # google-auth - # requests -uvicorn==0.23.1 - # via -r requirements-dev.in -virtualenv==20.24.2 - # via nox + # nox + # pre-commit watchdog==3.0.0 # via mkdocs -wheel==0.41.0 +wheel==0.41.2 # via - # -r requirements-dev.in + # modelkit (pyproject.toml) # pip-tools yarl==1.9.2 - # via - # -c requirements.txt - # aiohttp + # via aiohttp # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/requirements-optional.txt b/requirements-optional.txt deleted file mode 100644 index 61ca7386..00000000 --- a/requirements-optional.txt +++ /dev/null @@ -1,12 +0,0 @@ --r "requirements-dev.txt" - -numpy -grpcio -tensorflow -tensorflow-serving-api -# assets-s3 -boto3 -# assets-gcs -google-cloud-storage -# assets-az -azure-storage-blob \ No newline at end of file diff --git a/requirements.in b/requirements.in deleted file mode 100644 index 33f5925e..00000000 --- a/requirements.in +++ /dev/null @@ -1,16 +0,0 @@ -aiohttp -asgiref -cachetools -click -filelock -humanize -pydantic<2.0 -python-dateutil -redis -requests -rich -sniffio -structlog -tenacity -typing_extensions -tabulate \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 1b0631b2..00000000 --- a/requirements.txt +++ /dev/null @@ -1,78 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-emit-index-url --output-file=requirements.txt -# -aiohttp==3.8.5 - # via -r requirements.in -aiosignal==1.3.1 - # via aiohttp -asgiref==3.7.2 - # via -r requirements.in -async-timeout==4.0.2 - # via - # aiohttp - # redis -attrs==23.1.0 - # via aiohttp -cachetools==5.3.1 - # via -r requirements.in -certifi==2023.7.22 - # via requests -charset-normalizer==3.2.0 - # via - # aiohttp - # requests -click==8.1.6 - # via -r requirements.in -filelock==3.12.2 - # via -r requirements.in -frozenlist==1.4.0 - # via - # aiohttp - # aiosignal -humanize==4.7.0 - # via -r requirements.in -idna==3.4 - # via - # requests - # yarl -markdown-it-py==3.0.0 - # via rich -mdurl==0.1.2 - # via markdown-it-py -multidict==6.0.4 - # via - # aiohttp - # yarl -pydantic==1.10.12 - # via -r requirements.in -pygments==2.15.1 - # via rich -python-dateutil==2.8.2 - # via -r requirements.in -redis==4.6.0 - # via -r requirements.in -requests==2.31.0 - # via -r requirements.in -rich==13.4.2 - # via -r requirements.in -six==1.16.0 - # via python-dateutil -sniffio==1.3.0 - # via -r requirements.in -structlog==23.1.0 - # via -r requirements.in -tabulate==0.9.0 - # via -r requirements.in -tenacity==8.2.2 - # via -r requirements.in -typing-extensions==4.7.1 - # via - # -r requirements.in - # pydantic -urllib3==1.26.12 - # via requests -yarl==1.9.2 - # via aiohttp diff --git a/scripts/ci_tests.sh b/scripts/ci_tests.sh deleted file mode 100755 index d0a82ef6..00000000 --- a/scripts/ci_tests.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -ux - -if [[ $OS_NAME == "ubuntu-latest" ]]; then - export ENABLE_TF_SERVING_TEST=True - export ENABLE_TF_TEST=True - export ENABLE_REDIS_TEST=True - export ENABLE_S3_TEST=True - export ENABLE_GCS_TEST=True - export ENABLE_AZ_TEST=True - nox --error-on-missing-interpreters -s "coverage-${PYTHON_VERSION}" -else - nox --error-on-missing-interpreters -s "test-${PYTHON_VERSION}" -fi diff --git a/scripts/lint-apply.sh b/scripts/lint-apply.sh deleted file mode 100755 index a2aee0ba..00000000 --- a/scripts/lint-apply.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -x - -black modelkit tests bin -isort modelkit tests bin diff --git a/scripts/lint.sh b/scripts/lint.sh deleted file mode 100755 index 800e84e8..00000000 --- a/scripts/lint.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -if [[ $1 == apply ]] ; then - black modelkit tests bin - isort modelkit tests bin -fi - -black modelkit tests bin --check -isort modelkit tests bin --check-only -flake8 modelkit tests bin -mypy modelkit bin - diff --git a/scripts/test.sh b/scripts/test.sh deleted file mode 100755 index 02171e80..00000000 --- a/scripts/test.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -x - -coverage run -m pytest -coverage report -m -coverage xml \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 062a8dab..00000000 --- a/setup.cfg +++ /dev/null @@ -1,115 +0,0 @@ -[bumpversion] -current_version = 0.0.30 -commit = True -tag = True - -[metadata] -name = modelkit -description = Machine learning lib. -long_description = file: README.md, HISTORY.md -long_description_content_type = text/markdown -classifiers = - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 -home-page = https://idpy.org -project_urls = - Bug Tracker = https://github.com/Cornerstone-OnDemand/modelkit/issues - Documentation = https://Cornerstone-OnDemand.github.io/modelkit/ - Source Code = https://github.com/Cornerstone-OnDemand/modelkit - -[options] -zip_safe = False -include_package_data = True -packages = find: -install_requires = - aiohttp - asgiref - cachetools - click - filelock - humanize - pydantic<2.0 - python-dateutil - redis - requests - rich - sniffio - structlog - tenacity - typing_extensions - tabulate - -[options.extras_require] -tensorflow = - grpcio - tensorflow - tensorflow-serving-api -cli = - networkx - memory-profiler - fastapi - uvicorn -api = - fastapi - uvicorn -assets-s3 = - boto3 -assets-gcs = - google-cloud-storage -assets-az = - azure-storage-blob - -[options.packages.find] -where = . -exclude = - tests - -[options.package_data] -modelkit = py.typed - -[tool:pytest] -addopts = - --strict - --verbose - --tb=native - -vv - --failed-first - --disable-warnings - --durations 10 - --color=yes - tests - -[tool:isort] -profile = black - -[flake8] -max-line-length = 88 -extend-ignore = E203 - -[coverage:run] -source = modelkit -omit = - modelkit/assets/cli.py - modelkit/cli.py - -[coverage:report] -fail_under = 90 -precision = 2 - -[mypy] -ignore_missing_imports = True -plugins = pydantic.mypy - -[mypy-azure.storage.blob.*] -ignore_errors = True - -[options.entry_points] -console_scripts = - modelkit = modelkit.cli:modelkit_cli - -[bumpversion:file:modelkit/__init__.py] - -[bumpversion:file:setup.py] diff --git a/setup.py b/setup.py deleted file mode 100644 index 6a606965..00000000 --- a/setup.py +++ /dev/null @@ -1,5 +0,0 @@ -import setuptools - -setuptools.setup( - version="0.0.30" -) diff --git a/tests/assets/test_assetsmanager.py b/tests/assets/test_assetsmanager.py index 2b3034ac..3a66548f 100644 --- a/tests/assets/test_assetsmanager.py +++ b/tests/assets/test_assetsmanager.py @@ -5,6 +5,7 @@ import pytest import modelkit.assets.cli +from modelkit.assets import errors from modelkit.assets.manager import AssetsManager, _success_file_path from modelkit.assets.remote import StorageProvider from tests.conftest import skip_unless @@ -67,7 +68,7 @@ def _perform_mng_test(mng): assert d["from_cache"] # attempt to overwrite the asset - with pytest.raises(Exception): + with pytest.raises(errors.AssetAlreadyExistsError): mng.storage_provider.push( os.path.join(data_path, "some_data_in_folder.json"), "category-test/some-data.ext", diff --git a/tests/assets/test_assetsmanager_versioning.py b/tests/assets/test_assetsmanager_versioning.py index 9c06d95c..186ad902 100644 --- a/tests/assets/test_assetsmanager_versioning.py +++ b/tests/assets/test_assetsmanager_versioning.py @@ -13,7 +13,7 @@ def _perform_mng_test(mng): # test dry run for new asset data_path = os.path.join(test_path, "testdata", "some_data.json") mng.storage_provider.new(data_path, "category-test/some-data", "0.0", dry_run=True) - with pytest.raises(Exception): + with pytest.raises(errors.ObjectDoesNotExistError): mng.fetch_asset("category-test/some-data") # test updating an inexistant asset @@ -30,7 +30,7 @@ def _perform_mng_test(mng): mng.storage_provider.update( data_path, "category-test/some-data", version="0.1", dry_run=True ) - with pytest.raises(Exception): + with pytest.raises(errors.ObjectDoesNotExistError): mng.fetch_asset("category-test/some-data:0.1") # update the asset diff --git a/tests/test_async.py b/tests/test_async.py index fd2a5f05..e112dcfe 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -46,7 +46,7 @@ class ComposedModel(Model): def _predict(self, item, **kwargs): # The following does not currently work, because AsyncToSync does not # seem to correctly wrap asynchronous generators - for r in AsyncToSync( + for r in AsyncToSync( # noqa: B007 self.model_dependencies["async_model"].async_model.predict_gen )(iter((item,))): break diff --git a/tests/test_batching.py b/tests/test_batching.py index 72c6b2a2..fdf5bbff 100644 --- a/tests/test_batching.py +++ b/tests/test_batching.py @@ -138,8 +138,7 @@ def _callback_gen(batch_step, batch_items, batch_results): def _do_gen_test(m, batch_size, n_items): def item_iterator(): - for x in range(n_items): - yield x + yield from range(n_items) for value, position_in_batch, batch_len in m.predict_gen( item_iterator(), batch_size=batch_size @@ -169,8 +168,7 @@ def _predict_batch(self, items): async def _do_gen_test_async(m, batch_size, n_items): def item_iterator(): - for x in range(n_items): - yield x + yield from range(n_items) async for value, position_in_batch, batch_len in m.predict_gen( item_iterator(), batch_size=batch_size @@ -218,7 +216,7 @@ def _callback(batch_step, batch_items, batch_results): steps += 1 m = SomeModel() - for i, prediction in enumerate( + for i, _prediction in enumerate( m.predict_gen(range(100), _callback=_callback, batch_size=batch_size) ): if i + 1 == batch_break: diff --git a/tests/test_core.py b/tests/test_core.py index 395a5334..422a856c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3,7 +3,7 @@ import pytest -from modelkit.assets.errors import InvalidAssetSpecError +from modelkit.assets.errors import AssetDoesNotExistError, InvalidAssetSpecError from modelkit.core import errors from modelkit.core.library import ( ConfigurationNotFoundException, @@ -79,7 +79,7 @@ def _predict(self, item, **kwargs): "dep_model": ModelConfiguration(model_type=TestDepModel), } # The asset does not exist - with pytest.raises(Exception): + with pytest.raises(AssetDoesNotExistError): model_library = ModelLibrary( required_models=["some_asset"], configuration=config ) @@ -652,7 +652,9 @@ def _load(self): ).some_attribute with pytest.raises(ValueError): - self.model_dependencies.get("some_model", SomeModelDep).some_attribute + _ = self.model_dependencies.get( + "some_model", SomeModelDep + ).some_attribute def _predict(self, item): return item diff --git a/tests/test_errors.py b/tests/test_errors.py index 4ecb3c75..9dd695ab 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -100,7 +100,7 @@ async def test_prediction_error_async(monkeypatch, model): assert len(excinfo.traceback) <= 3 with pytest.raises(CustomError) as excinfo: - async for x in model.predict_gen(iter(({},))): + async for _x in model.predict_gen(iter(({},))): pass assert len(excinfo.traceback) <= 3 @@ -118,7 +118,7 @@ async def test_prediction_error_complex_tb_async(monkeypatch, model): assert len(excinfo.traceback) > 3 with pytest.raises(CustomError) as excinfo: - async for x in model.predict_gen(iter(({},))): + async for _x in model.predict_gen(iter(({},))): pass assert len(excinfo.traceback) > 3 diff --git a/tests/test_tf_model.py b/tests/test_tf_model.py index 291b45d4..3ed4905e 100644 --- a/tests/test_tf_model.py +++ b/tests/test_tf_model.py @@ -294,7 +294,7 @@ def _compare_models(model0, model1, items_and_results, tolerance=1e-2): assert compare_result(res_model0, result, tolerance) assert compare_result(res_model0, res_model1, tolerance) except AssertionError as e: - raise AssertionError(f"Models differ on single items\n{e.args[0]}") + raise AssertionError(f"Models differ on single items\n{e.args[0]}") from e items = [item for item, _ in items_and_results] try: @@ -306,7 +306,7 @@ def _compare_models(model0, model1, items_and_results, tolerance=1e-2): res_model1 = res_model1_items[k] assert compare_result(res_model0, res_model1, tolerance) except AssertionError as e: - raise AssertionError(f"Models differ on item batches\n{e.args[0]}") + raise AssertionError(f"Models differ on item batches\n{e.args[0]}") from e try: # Compare batched vs. computed with one item @@ -317,7 +317,7 @@ def _compare_models(model0, model1, items_and_results, tolerance=1e-2): except AssertionError as e: raise AssertionError( f"Models predictions on single and batches differ\n{e.args[0]}" - ) + ) from e @pytest.mark.asyncio @@ -364,7 +364,7 @@ async def _compare_models_async(model, model_async, items_and_results, tolerance assert compare_result(res_model0, result, tolerance) assert compare_result(res_model0, res_model1, tolerance) except AssertionError as e: - raise AssertionError(f"Models differ on single items\n{e.args[0]}") + raise AssertionError(f"Models differ on single items\n{e.args[0]}") from e items = [item for item, _ in items_and_results] try: @@ -376,7 +376,7 @@ async def _compare_models_async(model, model_async, items_and_results, tolerance res_model1 = res_model1_items[k] assert compare_result(res_model0, res_model1, tolerance) except AssertionError as e: - raise AssertionError(f"Models differ on item batches\n{e.args[0]}") + raise AssertionError(f"Models differ on item batches\n{e.args[0]}") from e try: # Compare batched vs. computed with one item @@ -387,7 +387,7 @@ async def _compare_models_async(model, model_async, items_and_results, tolerance except AssertionError as e: raise AssertionError( f"Models predictions on single and batches differ\n{e.args[0]}" - ) + ) from e @skip_unless("ENABLE_TF_TEST", "True") diff --git a/tests/test_validate.py b/tests/test_validate.py index b4923e2c..fc2befd5 100644 --- a/tests/test_validate.py +++ b/tests/test_validate.py @@ -258,11 +258,15 @@ class ListModel(pydantic.BaseModel): try: ListModel(values=["ok"] * 100) except pydantic.error_wrappers.ValidationError as exc: - raise ModelkitDataValidationException("test error", pydantic_exc=exc) + raise ModelkitDataValidationException( + "test error", pydantic_exc=exc + ) from exc # This will not with pytest.raises(ModelkitDataValidationException): try: ListModel(values=["ok"]) except pydantic.error_wrappers.ValidationError as exc: - raise ModelkitDataValidationException("test error", pydantic_exc=exc) + raise ModelkitDataValidationException( + "test error", pydantic_exc=exc + ) from exc diff --git a/tests/testdata/mocked_service.py b/tests/testdata/mocked_service.py index 60f72432..d9a02933 100644 --- a/tests/testdata/mocked_service.py +++ b/tests/testdata/mocked_service.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Union import fastapi from starlette.responses import JSONResponse @@ -8,7 +8,7 @@ @app.post("/api/path/endpoint") async def some_endpoint( - item: Dict[str, str], + item: Dict[str, Union[str, int]], limit: Optional[int] = None, skip: Optional[int] = None, ): @@ -21,7 +21,7 @@ async def some_endpoint( @app.post("/api/path/endpoint/batch") async def some_endpoint_batch( - items: List[Dict[str, str]], + items: List[Dict[str, Union[str, int]]], limit: Optional[int] = None, skip: Optional[int] = None, ):