Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,46 @@

# Do not edit below this line. Edits will be overwritten by gen_gitattributes.sh

catalog/clients/python/src/catalog_openapi/__init__.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/api/model_catalog_service_api.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/api_client.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/configuration.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/exceptions.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/__init__.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/artifact_type_query_param.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/base_model.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/base_resource.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/base_resource_dates.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/base_resource_list.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_artifact.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_artifact_list.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_label.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_label_list.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_metrics_artifact.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_model.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_model_artifact.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_model_list.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_source.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_source_list.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_source_preview_response.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_source_preview_response_all_of_summary.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/catalog_source_status.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/error.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/field_filter.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/filter_option.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/filter_option_range.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/filter_options_list.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/metadata_bool_value.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/metadata_double_value.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/metadata_int_value.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/metadata_proto_value.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/metadata_string_value.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/metadata_struct_value.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/metadata_value.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/model_preview_result.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/order_by_field.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/models/sort_order.py linguist-generated=true
catalog/clients/python/src/catalog_openapi/rest.py linguist-generated=true
catalog/internal/server/openapi/api.go linguist-generated=true
catalog/internal/server/openapi/api_model_catalog_service.go linguist-generated=true
catalog/internal/server/openapi/error.go linguist-generated=true
Expand Down
54 changes: 54 additions & 0 deletions catalog/clients/python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.egg-info/

# Type checking
.mypy_cache/

# Testing
.pytest_cache/
/.coverage*
htmlcov/
.hypothesis/
.schemathesis/

# Build
/dist/
/build/

# Virtual environments
venv/
.venv/
env/

# Nox
/.nox/

# Ruff
.ruff_cache/

# Documentation
/docs/_build/

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# Poetry
.python-version

# Logs and temp files
*.log
*.tmp

# Kubernetes/Deployment
.port-forward.pid

# Screenshots from browser testing
*.png
*.jpeg
241 changes: 241 additions & 0 deletions catalog/clients/python/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# Makefile for Model Catalog Python Client

all: install tidy

.PHONY: help
help:
@echo "Model Catalog Python Client Targets:"
@echo ""
@echo " Setup & Build:"
@echo " install - Generate OpenAPI client and install dependencies"
@echo " generate - Regenerate catalog API client from OpenAPI spec"
@echo " clean - Remove generated code and caches"
@echo " build - Build the package"
@echo ""
@echo " Testing:"
@echo " test-e2e - Run E2E tests (requires running catalog)"
@echo " test-fuzz - Run fuzz tests (requires running catalog)"
@echo ""
@echo " Code Quality:"
@echo " lint - Run linters (ruff + mypy)"
@echo " tidy - Auto-fix code style issues"
@echo ""
@echo " Nox Sessions (multi-Python testing):"
@echo " nox - Run default nox sessions"
@echo " nox-lint - Run lint on all Python versions"
@echo " nox-e2e - Run E2E tests on all Python versions"
@echo ""
@echo " Deployment (K8s with Kustomize):"
@echo " deploy - Full local deployment (Kind + build + deploy)"
@echo " deploy-kind - Create Kind cluster"
@echo " deploy-build - Build Docker image"
@echo " deploy-load - Load image into Kind cluster"
@echo " deploy-k8s - Deploy to existing cluster (no Kind, no build)"
@echo " deploy-apply - Apply kustomize manifests"
@echo " deploy-forward - Start port-forward"
@echo " deploy-restart - Rebuild and restart catalog (quick dev cycle)"
@echo " deploy-cleanup - Remove deployment and Kind cluster"

# Configuration (can be overridden: make deploy CATALOG_PORT=9090)
export CATALOG_NAMESPACE ?= model-catalog
export CATALOG_IMAGE ?= model-registry:catalog-test
export CLUSTER_NAME ?= catalog-e2e
export CATALOG_PORT ?= 8081

# Kustomize overlay for E2E testing
KUSTOMIZE_E2E := ../../../manifests/kustomize/options/catalog/overlays/e2e

.PHONY: install
install: generate
poetry install

.PHONY: generate
generate:
@echo "Generating Catalog API client from OpenAPI spec..."
@mkdir -p src/catalog_openapi
../../../bin/openapi-generator-cli generate \
-i ../../../api/openapi/catalog.yaml \
-g python \
-o src/ \
--package-name catalog_openapi \
--additional-properties=library=urllib3,generateSourceCodeOnly=true,useOneOfDiscriminatorLookup=true
@# Remove generated test and docs
@rm -rf src/catalog_openapi/test src/catalog_openapi/docs
@rm -f src/catalog_openapi_README.md
@if [ -d patches ] && [ -n "$$(ls -A patches/*.patch 2>/dev/null)" ]; then \
echo "Applying patches..."; \
git apply patches/*.patch; \
fi
@echo "[OK] Generated catalog_openapi package"

.PHONY: clean
clean:
@echo "Cleaning generated code and caches..."
rm -rf src/catalog_openapi/
rm -rf .pytest_cache/
rm -rf .ruff_cache/
rm -rf .mypy_cache/
rm -rf .nox/
rm -rf .coverage
rm -rf .coverage.*
rm -rf htmlcov/
rm -rf dist/
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
find . -type f -name "*.pyc" -delete
@echo "[OK] Cleaned"

.PHONY: test-e2e
test-e2e:
CATALOG_URL=$${CATALOG_URL:-http://localhost:$(CATALOG_PORT)} poetry run pytest --e2e -v -rA

.PHONY: test-fuzz
test-fuzz:
CATALOG_URL=$${CATALOG_URL:-http://localhost:$(CATALOG_PORT)} poetry run pytest --fuzz -v

.PHONY: lint
lint:
poetry run ruff check .
poetry run mypy .

.PHONY: tidy
tidy:
@echo "Fixing code style issues..."
-poetry run ruff check --fix-only . src/catalog_openapi 2>/dev/null
-poetry run ruff format src tests 2>/dev/null

.PHONY: build
build: install tidy
poetry build

.PHONY: update
update:
poetry lock

# Nox sessions for multi-Python version testing
.PHONY: nox
nox:
poetry run nox

.PHONY: nox-lint
nox-lint:
poetry run nox -s lint

.PHONY: nox-e2e
nox-e2e:
poetry run nox -s e2e

# =============================================================================
# Deployment Targets
# =============================================================================

# Full local deployment: Kind cluster + build + deploy everything
.PHONY: deploy
deploy: deploy-kind deploy-build deploy-load deploy-k8s deploy-forward
@echo ""
@echo "========================================"
@echo "[OK] Deployment complete!"
@echo "========================================"
@echo ""
@echo "Catalog URL: http://localhost:$(CATALOG_PORT)"
@echo ""
@echo "Run tests: make test-e2e"
@echo "View logs: kubectl logs -f deployment/model-catalog-server -n $(CATALOG_NAMESPACE)"
@echo "Cleanup: make deploy-cleanup"

# Deploy to existing cluster (no Kind, no build)
.PHONY: deploy-k8s
deploy-k8s: deploy-namespace deploy-apply

# Create Kind cluster
.PHONY: deploy-kind
deploy-kind:
@echo "Setting up Kind cluster: $(CLUSTER_NAME)"
@if kind get clusters 2>/dev/null | grep -q "$(CLUSTER_NAME)"; then \
echo "Cluster $(CLUSTER_NAME) already exists, using it"; \
kubectl config use-context "kind-$(CLUSTER_NAME)"; \
else \
kind create cluster -n "$(CLUSTER_NAME)"; \
fi

# Build Docker image
.PHONY: deploy-build
deploy-build:
@echo "Building Docker image: $(CATALOG_IMAGE)"
cd ../../.. && docker build --progress=plain -t "$(CATALOG_IMAGE)" -f Dockerfile .
@echo "[OK] Build complete"

# Load image into Kind cluster
.PHONY: deploy-load
deploy-load:
@echo "Loading image into Kind cluster..."
kind load docker-image -n "$(CLUSTER_NAME)" "$(CATALOG_IMAGE)" 2>&1 | tail -5
@# Adjust image name for podman if needed
@if docker --version 2>&1 | grep -q "podman"; then \
echo "Using podman, image will be referenced as localhost/$(CATALOG_IMAGE)"; \
fi

# Create namespace
.PHONY: deploy-namespace
deploy-namespace:
@echo "Creating namespace: $(CATALOG_NAMESPACE)"
kubectl create namespace "$(CATALOG_NAMESPACE)" --dry-run=client -o yaml | kubectl apply -f -

# Apply kustomize manifests (includes postgres, secrets, configmaps, catalog)
.PHONY: deploy-apply
deploy-apply: deploy-namespace
@echo "Applying kustomize manifests..."
@# Set the image dynamically to support both Docker and Podman
cd "$(KUSTOMIZE_E2E)" && kustomize edit set image "ghcr.io/kubeflow/model-registry/server=$(CATALOG_IMAGE)"
kubectl apply -k "$(KUSTOMIZE_E2E)"
@echo "Waiting for PostgreSQL to be ready..."
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=postgres -n "$(CATALOG_NAMESPACE)" --timeout=120s
@echo "[OK] PostgreSQL is ready"
@echo "Waiting for Catalog to be ready..."
kubectl wait --for=condition=available deployment/model-catalog-server -n "$(CATALOG_NAMESPACE)" --timeout=120s
@echo "[OK] Catalog service is ready"

# Start port-forward
.PHONY: deploy-forward
deploy-forward:
@echo "Starting port-forward on port $(CATALOG_PORT)..."
@# Stop any existing port-forward first to avoid stale PIDs
@if [ -f .port-forward.pid ]; then \
kill $$(cat .port-forward.pid) 2>/dev/null || true; \
rm -f .port-forward.pid; \
fi
@kubectl port-forward -n "$(CATALOG_NAMESPACE)" svc/model-catalog "$(CATALOG_PORT)":8080 > /dev/null 2>&1 & echo $$! > .port-forward.pid
@sleep 3
@if curl -s "http://localhost:$(CATALOG_PORT)/api/model_catalog/v1alpha1/sources" > /dev/null 2>&1; then \
echo "[OK] Catalog accessible at http://localhost:$(CATALOG_PORT)"; \
else \
echo "[WARN] Port-forward may not be ready. Try: kubectl port-forward -n $(CATALOG_NAMESPACE) svc/model-catalog $(CATALOG_PORT):8080"; \
fi

# Stop port-forward
.PHONY: deploy-forward-stop
deploy-forward-stop:
@echo "Stopping port-forward..."
@if [ -f .port-forward.pid ]; then \
kill $$(cat .port-forward.pid) 2>/dev/null || true; \
rm -f .port-forward.pid; \
fi

# Cleanup everything
.PHONY: deploy-cleanup
deploy-cleanup: deploy-forward-stop
@echo "Cleaning up deployment..."
@if kubectl get namespace "$(CATALOG_NAMESPACE)" &>/dev/null; then \
kubectl delete namespace "$(CATALOG_NAMESPACE)" --timeout=60s || true; \
fi
@if kind get clusters 2>/dev/null | grep -q "$(CLUSTER_NAME)"; then \
echo "Deleting Kind cluster: $(CLUSTER_NAME)"; \
kind delete cluster -n "$(CLUSTER_NAME)"; \
fi
@echo "[OK] Cleanup complete"

# Shortcut: redeploy catalog only (after code changes)
.PHONY: deploy-restart
deploy-restart: deploy-build deploy-load
kubectl rollout restart deployment/model-catalog-server -n "$(CATALOG_NAMESPACE)"
kubectl rollout status deployment/model-catalog-server -n "$(CATALOG_NAMESPACE)"
@echo "[OK] Catalog restarted"
Loading
Loading