File size: 7,756 Bytes
418f5a0
 
 
 
e1aca60
a0aff5b
 
 
 
5cbfffe
 
 
 
a0aff5b
 
e1aca60
 
 
418f5a0
 
 
e1aca60
 
a0aff5b
 
 
418f5a0
 
 
a0aff5b
 
 
 
e1aca60
 
a0aff5b
e1aca60
 
 
 
 
7f99e2c
e1aca60
5cbfffe
 
 
418f5a0
 
 
7f99e2c
 
 
e1aca60
 
7f99e2c
5cbfffe
e1aca60
 
a0aff5b
7f99e2c
 
418f5a0
 
 
7f99e2c
 
 
 
e1aca60
418f5a0
 
 
e1aca60
7f99e2c
 
 
 
 
5cbfffe
e1aca60
 
a0aff5b
5cbfffe
e1aca60
418f5a0
 
 
7f99e2c
 
418f5a0
 
 
 
 
 
 
 
3dcdaae
 
 
 
418f5a0
 
 
e1aca60
a0aff5b
e1aca60
 
418f5a0
 
 
e1aca60
a0aff5b
e1aca60
 
418f5a0
 
 
e1aca60
 
 
 
 
 
 
 
418f5a0
 
 
e1aca60
 
7f99e2c
e1aca60
 
a0aff5b
e1aca60
2e3e9b8
418f5a0
 
 
 
2e3e9b8
418f5a0
2e3e9b8
 
418f5a0
2e3e9b8
 
 
 
 
 
 
 
 
 
 
418f5a0
2e3e9b8
5e6809d
418f5a0
5e6809d
 
418f5a0
5e6809d
418f5a0
 
 
5e6809d
418f5a0
5e6809d
 
418f5a0
 
5e6809d
418f5a0
5e6809d
418f5a0
5e6809d
 
418f5a0
5e6809d
 
418f5a0
5e6809d
 
418f5a0
5e6809d
 
 
 
418f5a0
 
5e6809d
418f5a0
5e6809d
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# ==============================================================
# Makefile β€” NL2SQL Copilot
# ==============================================================

# ---------- Config ----------
VENV_DIR   ?= .venv
PY         ?= $(if $(wildcard $(VENV_DIR)/bin/python),$(VENV_DIR)/bin/python,python3)
PIP        ?= $(if $(wildcard $(VENV_DIR)/bin/pip),$(VENV_DIR)/bin/pip,pip)
UVICORN    ?= $(if $(wildcard $(VENV_DIR)/bin/uvicorn),$(VENV_DIR)/bin/uvicorn,uvicorn)
RUFF       ?= $(if $(wildcard $(VENV_DIR)/bin/ruff),$(VENV_DIR)/bin/ruff,ruff)
MYPY       ?= $(if $(wildcard $(VENV_DIR)/bin/mypy),$(VENV_DIR)/bin/mypy,mypy)
PYTEST     ?= $(if $(wildcard $(VENV_DIR)/bin/pytest),$(VENV_DIR)/bin/pytest,pytest)

DOCKER_IMG ?= nl2sql-copilot
PORT       ?= 8000

.DEFAULT_GOAL := help

# ==============================================================
# Meta
# ==============================================================
.PHONY: help
help: ## Show this help
	@printf "\n\033[1mAvailable targets:\033[0m\n"
	@awk 'BEGIN {FS = ":.*##"} /^[[:alnum:]_.-]+:.*##/ {printf "  \033[36m%-18s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

# ==============================================================
# Setup
# ==============================================================
.PHONY: venv
venv: ## Create virtual environment in .venv/
	python3 -m venv $(VENV_DIR)
	$(PIP) install --upgrade pip wheel

.PHONY: install
install: ## Install runtime dependencies inside venv
	$(PIP) install -r requirements.txt

.PHONY: dev-install
dev-install: ## Install dev tools (ruff, mypy, pytest, coverage, uvicorn, etc.)
	$(PIP) install -U pip wheel
	$(PIP) install ruff mypy pytest pytest-cov uvicorn pre-commit

.PHONY: bootstrap
bootstrap: venv dev-install ## Create venv and install dev tools

# ==============================================================
# Quality β€” Read-only (CI)
# ==============================================================
.PHONY: fmt-check
fmt-check: ## Verify formatting without modifying files
	$(RUFF) format . --check

.PHONY: lint
lint: ## Run linting
	$(RUFF) check .

.PHONY: typecheck
typecheck: ## Run type checking only
	$(MYPY) . --ignore-missing-imports --explicit-package-bases

# ==============================================================
# Quality β€” Write mode (local dev)
# ==============================================================
.PHONY: format
format: ## Auto-format & fix with ruff
	$(RUFF) format .
	$(RUFF) check . --fix

# ==============================================================
# Tests
# ==============================================================
.PHONY: test
test: ## Run fast test suite (exclude slow)
	PYTHONPATH=$$PWD $(PYTEST) -q -m "not slow"

.PHONY: test-all
test-all: ## Run full test suite including slow tests
	PYTHONPATH=$$PWD $(PYTEST) -q

.PHONY: cov
cov: ## Run tests with coverage
	PYTHONPATH=$$PWD $(PYTEST) --cov=nl2sql --cov-report=term-missing

# ==============================================================
# Unified gate for CI
# ==============================================================
.PHONY: check
check: ## Run format check, lint, typecheck, and fast tests
	$(MAKE) fmt-check
	$(MAKE) lint
	$(MAKE) typecheck
	$(MAKE) test

# ==============================================================
# Pre-commit
# ==============================================================
.PHONY: precommit
precommit: ## Run all pre-commit hooks on all files
	pre-commit run --all-files

# ==============================================================
# Run app
# ==============================================================
.PHONY: run
run: ## Run FastAPI app (reload mode)
	$(UVICORN) app.main:app --reload --host 0.0.0.0 --port $(PORT)

# ==============================================================
# Benchmarks
# ==============================================================
.PHONY: bench
bench: ## Run benchmark suite (DummyLLM fallback)
	$(PY) -m benchmarks.run

# ==============================================================
# Docker
# ==============================================================
.PHONY: docker-build
docker-build: ## Build Docker image
	docker build -t $(DOCKER_IMG) .

.PHONY: docker-run
docker-run: ## Run Docker container on port $(PORT)
	docker run --rm -p $(PORT):8000 $(DOCKER_IMG)

# ==============================================================
# Clean
# ==============================================================
.PHONY: clean
clean: ## Remove Python caches
	rm -rf __pycache__ .pytest_cache .mypy_cache .ruff_cache

.PHONY: clean-all
clean-all: clean ## Remove build artifacts and coverage
	rm -rf dist build .coverage *.egg-info

# ==============================================================
# Observability Stack
# ==============================================================
.PHONY: obs-up obs-down obs-logs prom-up prom-check smoke grafana-import

prom-up: ## Bring up Prometheus + Grafana via Docker Compose
	docker compose -f docker-compose.prom.yml up -d

prom-check: ## Validate Prometheus configs (local or Docker fallback)
	@if command -v promtool >/dev/null 2>&1; then \
		echo "πŸ” Running promtool locally..."; \
		promtool check rules prometheus/rules.yml && promtool check config prometheus/prometheus.yml; \
	else \
		echo "⚠️ promtool not found, running via Docker..."; \
		docker run --rm -v $$(pwd)/prometheus:/etc/prometheus prom/prometheus \
			promtool check rules /etc/prometheus/rules.yml && \
		docker run --rm -v $$(pwd)/prometheus:/etc/prometheus prom/prometheus \
			promtool check config /etc/prometheus/prometheus.yml; \
	fi

smoke: ## Generate sample traffic and print key metrics snapshot
	./scripts/smoke_metrics.sh

obs-up: ## Start observability stack and verify readiness
	@set -e; \
	$(MAKE) prom-up; \
	echo "⏳ Waiting for Prometheus (http://localhost:9090)..."; \
	for i in $$(seq 1 30); do \
		if curl -fsS http://localhost:9090/-/ready >/dev/null 2>&1; then echo "βœ… Prometheus is ready"; break; fi; \
		if nc -z localhost 9090 >/dev/null 2>&1; then echo "βœ… Prometheus port is open (assuming ready)"; break; fi; \
		sleep 3; if [ $$i -eq 30 ]; then echo "❌ Prometheus did not become ready in time"; exit 1; fi; \
	done; \
	echo "⏳ Waiting for Grafana (http://localhost:3000)..."; \
	for i in $$(seq 1 30); do \
		code=$$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/login || true); \
		if [ "$$code" = "200" ] || [ "$$code" = "302" ]; then echo "βœ… Grafana is up"; break; fi; \
		sleep 3; if [ $$i -eq 30 ]; then echo "❌ Grafana did not become ready in time"; exit 1; fi; \
	done; \
	echo "πŸš€ Running smoke traffic..."; \
	$(MAKE) smoke; \
	echo "πŸŽ‰ Observability stack is live β†’ Prometheus: http://localhost:9090 , Grafana: http://localhost:3000"; \
	$(MAKE) grafana-import

obs-down: ## Tear down the observability stack
	docker compose -f docker-compose.prom.yml down

obs-logs: ## Tail logs of both services
	docker compose -f docker-compose.prom.yml logs -f

grafana-import: ## Import Grafana dashboard via HTTP API
	@set -e; \
	echo "⏳ Waiting for Grafana API to become ready..."; \
	for i in $$(seq 1 30); do \
		code=$$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/api/health || true); \
		if [ "$$code" = "200" ]; then echo "βœ… Grafana API is ready"; break; fi; \
		sleep 3; if [ $$i -eq 30 ]; then echo "❌ Grafana API did not become ready in time"; exit 1; fi; \
	done; \
	echo "πŸ“¦ Importing dashboard..."; \
	curl -s -X POST http://admin:admin@localhost:3000/api/dashboards/db \
		-H "Content-Type: application/json" \
		-d "{\"dashboard\": $$(cat prometheus/grafana_dashboard.json), \"overwrite\": true, \"folderId\": 0}" \
		| jq -r '.status' || true; \
	echo "πŸŽ‰ Dashboard imported β†’ http://localhost:3000/dashboards"