From c512bbdf66261c83c918d82d35d2faf87ce6f70f Mon Sep 17 00:00:00 2001 From: Ivan Murabito <36967518+CuriousDolphin@users.noreply.github.com> Date: Thu, 28 Nov 2024 17:13:52 +0100 Subject: [PATCH] feat: add CI/CD pipelines (#12) * feat: add simple tests * feat: add test pipeline * fix: add onnxruntime to dev requirements * feat: add release pipeline --- .github/workflows/release.yml | 35 ++++++++++++++++++ .github/workflows/test.yml | 35 ++++++++++++++++++ .gitignore | 1 + Makefile | 4 +- focoos/focoos.py | 4 +- pyproject.toml | 3 +- tests/__init__.py | 0 tests/test_focoos.py | 70 +++++++++++++++++++++++++++++++++++ 8 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml create mode 100644 tests/__init__.py create mode 100644 tests/test_focoos.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..587ecfb --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,35 @@ +name: Release +on: + push: + branches: + - main + workflow_dispatch: +jobs: + build: + runs-on: actions-runner-cpu + permissions: + contents: write + steps: + - uses: actions/checkout@v2 + - name: Bump version and push tag + id: tag_version + uses: mathieudutour/github-tag-action@v6.2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Update pyproject.toml with new version tag + run: | + sed -i 's/version = ".*"/version = "${{ steps.tag_version.outputs.new_tag }}"/' pyproject.toml + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "[bot]: update version to ${{ steps.tag_version.outputs.new_tag }}" + branch: main + file_pattern: pyproject.toml + add_options: --update + - name: Create a GitHub release + uses: ncipollo/release-action@v1 + with: + tag: ${{ steps.tag_version.outputs.new_tag }} + name: Release ${{ steps.tag_version.outputs.new_tag }} + body: ${{ steps.tag_version.outputs.changelog }} + generateReleaseNotes: true \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..ab75b9b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +name: Run Focoos tests +on: + pull_request: + workflow_dispatch: +env: + DOCKER_BUILDKIT: 1 + AWS_REGION: eu-west-1 +jobs: + Run-test: + runs-on: actions-runner-cpu + permissions: + id-token: write # This is required for requesting the JWT + contents: read + issues: write + pull-requests: write + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: install make + run: sudo apt-get update && sudo apt-get install ffmpeg libsm6 libxext6 make -y + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.12" + cache: "pip" + - name: Install dependencies + run: make install-dev + - name: Run test + run: make test + - name: Pytest coverage comment + uses: MishaKav/pytest-coverage-comment@main + with: + pytest-xml-coverage-path: ./tests/coverage.xml + junitxml-path: ./tests/junit.xml + report-only-changed-files: true diff --git a/.gitignore b/.gitignore index 4234fc1..5df330f 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,4 @@ ipython_config.py notebooks/.data .venv /data +tests/junit.xml \ No newline at end of file diff --git a/Makefile b/Makefile index 1ccb3f8..48ffa13 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ install: @pip install . --no-cache-dir install-dev: - @pip install -e ".[dev]" --no-cache-dir + @pip install -e ".[inference,dev]" --no-cache-dir install-pre-commit: @pre-commit install @@ -14,6 +14,8 @@ lint: @black . run-pre-commit: @pre-commit run --all-files +test: + @pytest -s --cov=focoos --cov-report="xml:tests/coverage.xml" --junitxml=./tests/junit.xml && rm -f .coverage clean: @rm -rf build dist *.egg-info .tox .nox .coverage .coverage.* .cache .pytest_cache htmlcov @find . -type d -name "__pycache__" -exec rm -r {} + diff --git a/focoos/focoos.py b/focoos/focoos.py index 8b29968..2f29623 100644 --- a/focoos/focoos.py +++ b/focoos/focoos.py @@ -26,14 +26,14 @@ class Focoos: def __init__( self, api_key: str = config.focoos_api_key, # type: ignore - host_url: FocoosEnvHostUrl = config.default_host_url, + host_url: str = config.default_host_url, ): self.api_key = api_key if not self.api_key: logger.error("API key is required 🤖") raise ValueError("API key is required 🤖") - self.http_client = HttpClient(api_key, host_url.value) + self.http_client = HttpClient(api_key, host_url) self.user_info = self._get_user_info() self.cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "focoos") logger.info( diff --git a/pyproject.toml b/pyproject.toml index f03d302..3d2ac54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ include = ["focoos**"] [project] name = "focoos" +version = "0.0.1" description = "Focoos SDK" readme = "README.md" requires-python = ">=3.10" @@ -20,7 +21,7 @@ dependencies = [ "numpy~=1.26.4", "scipy~=1.14.1", ] -version = "0.0.1" + authors = [{ name = "focoos.ai", email = "info@focoos.ai" }] keywords = [ "computer_vision", diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_focoos.py b/tests/test_focoos.py new file mode 100644 index 0000000..893f767 --- /dev/null +++ b/tests/test_focoos.py @@ -0,0 +1,70 @@ +import pytest +from unittest.mock import patch, MagicMock +from focoos.ports import ModelMetadata, DatasetMetadata, ModelPreview +from focoos.focoos import Focoos +from focoos.utils.system import HttpClient + + +@pytest.fixture +def focoos_instance(): + with patch("focoos.focoos.HttpClient") as MockHttpClient: + mock_client = MockHttpClient.return_value + mock_client.get.return_value.status_code = 200 + mock_client.get.return_value.json.return_value = {"email": "test@example.com"} + return Focoos(api_key="test_api_key", host_url="http://mock-host-url.com") + + +def test_focoos_initialization(focoos_instance): + assert focoos_instance.api_key == "test_api_key" + assert focoos_instance.user_info["email"] == "test@example.com" + + +def test_get_model_info(focoos_instance): + mock_response = { + "name": "test-model", + "ref": "model-ref", + "owner_ref": "pytest", + "focoos_model": "focoos_rtdetr", + "created_at": "2024-01-01", + "updated_at": "2024-01-01", + "description": "Test model description", + "task": "detection", + "status": "TRAINING_COMPLETED", + } + focoos_instance.http_client.get = MagicMock( + return_value=MagicMock(status_code=200, json=lambda: mock_response) + ) + model_info = focoos_instance.get_model_info("test-model") + assert model_info.name == "test-model" + assert model_info.ref == "model-ref" + assert model_info.description == "Test model description" + + +def test_list_models(focoos_instance): + mock_response = [ + { + "name": "model1", + "ref": "ref1", + "task": "detection", + "description": "model1 description", + "status": "TRAINING_COMPLETED", + "focoos_model": "focoos_rtdetr", + }, + { + "name": "model2", + "ref": "ref2", + "task": "detection", + "description": "model2 description", + "status": "TRAINING_RUNNING", + "focoos_model": "focoos_rtdetr", + }, + ] + + focoos_instance.http_client.get = MagicMock( + return_value=MagicMock(status_code=200, json=lambda: mock_response) + ) + + models = focoos_instance.list_models() + assert len(models) == 2 + assert models[0].name == "model1" + assert models[1].ref == "ref2"